Pull to refresh

Comments 54

Производителю процессора сообщили?
Я бы начал с прогона мемтеста)
Спасибо за совет, попробую поставить built-in тест на ночь.
И еще интересно, это проблема конкретного экземпляра процессора, партии и т.д.? Все же нынче некоторый процент брака стал нормой, вдруг просто проскочил при тестировании.

Интересная проблема, попробуйте с 'strictfp' в Java или с аналогичными опциями clang. Смущает только, что слишком большая разница, как для ошибки округления, я бы продолжил поиски)

Честно говоря не знаю куда дальше продолжать :)

Добавление strictfp к бенчмарк классу в Java ошибку не убрало. Не уверен как это можно включить для clang. В интернете я нашел что можно проверить std::numeric_limits::is_iec559 — «возвращает» 1.

strictfp на современных процессорах ни на что не влияет. Его даже убрать хотят из джавы вообще.

Интересно какая версия java? Кто вендор? Спрашиваю из соображений проверить на своём макбуке
Версия HotSpot java version «1.8.0_171».
У коллег проблема не повторялась. Исходя из того что проблема появлялась очень редко и воспроизвелась на простом cpp методе, я думаю что дело именно в железе конкретного компьютера.
Я бы начал с того, что обновил операционку. Такая проблема скорее похожа на ошибку сохранения/восстановления контекста FPU, а не аппаратную. Кроме того, поскольку у вас есть изолированный пример на С++, попробуйте загрузить live-CD с линуксом и проверьте на этом же железе, но с другой OS.
Спасибо за совет.

— Обновить ОС не помогло.
— Я попробовал запустить докер контейнер с убунту и собрать бинарник там. Внутри контейнера ошибка не повторилась! (проверил gdb что sse инструкции используются). Проверить на реальном live cd пока нет возможности так как на ноутбуке только usb-c порты :( Проверю на «чистом» линуксе как появится возможность.
Внутри контейнера ошибка не повторилась! (проверил gdb что sse инструкции используются).
если не повторилась — значит точно не железо, а операционка.
Странно только почему (т.е. какая версия+билд ОС и какая версия фирмвари в ЦПУ)…
Версии
➜  ~ system_profiler SPSoftwareDataType
Software:

    System Software Overview:

      System Version: macOS 10.13.5 (17F77)
      Kernel Version: Darwin 17.6.0
      Boot Volume: Macintosh HD
      Boot Mode: Normal
      Computer Name: MacBook Pro (46)
      User Name: Aleksei Kutuzov (alekseik)
      Secure Virtual Memory: Enabled
      System Integrity Protection: Enabled
      Time since boot: 12:39

➜  ~ sysctl -a | grep machdep.cpu


machdep.cpu.max_basic: 22
machdep.cpu.max_ext: 2147483656
machdep.cpu.vendor: GenuineIntel
machdep.cpu.brand_string: Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz
machdep.cpu.family: 6
machdep.cpu.model: 142
machdep.cpu.extmodel: 8
machdep.cpu.extfamily: 0
machdep.cpu.stepping: 9
machdep.cpu.feature_bits: 9221959987971750911
machdep.cpu.leaf7_feature_bits: 43804591
machdep.cpu.extfeature_bits: 1241984796928
machdep.cpu.signature: 526057
machdep.cpu.brand: 0
machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTES64 MON DSCPL VMX EST TM2 SSSE3 FMA CX16 TPR PDCM SSE4.1 SSE4.2 x2APIC MOVBE POPCNT AES PCID XSAVE OSXSAVE SEGLIM64 TSCTMR AVX1.0 RDRAND F16C
machdep.cpu.leaf7_features: SMEP ERMS RDWRFSGS TSC_THREAD_OFFSET BMI1 AVX2 BMI2 INVPCID SMAP RDSEED ADX IPT SGX FPU_CSDS MPX CLFSOPT
machdep.cpu.extfeatures: SYSCALL XD 1GBPAGE EM64T LAHF LZCNT PREFETCHW RDTSCP TSCI
machdep.cpu.logical_per_package: 16
machdep.cpu.cores_per_package: 8
machdep.cpu.microcode_version: 132
machdep.cpu.processor_flag: 6
machdep.cpu.mwait.linesize_min: 64
machdep.cpu.mwait.linesize_max: 64
machdep.cpu.mwait.extensions: 3
machdep.cpu.mwait.sub_Cstates: 286531872
machdep.cpu.thermal.sensor: 1
machdep.cpu.thermal.dynamic_acceleration: 1
machdep.cpu.thermal.invariant_APIC_timer: 1
machdep.cpu.thermal.thresholds: 2
machdep.cpu.thermal.ACNT_MCNT: 1
machdep.cpu.thermal.core_power_limits: 1
machdep.cpu.thermal.fine_grain_clock_mod: 1
machdep.cpu.thermal.package_thermal_intr: 1
machdep.cpu.thermal.hardware_feedback: 0
machdep.cpu.thermal.energy_policy: 1
machdep.cpu.xsave.extended_state: 31 832 1088 0
machdep.cpu.xsave.extended_state1: 15 832 256 0
machdep.cpu.arch_perf.version: 4
machdep.cpu.arch_perf.number: 4
machdep.cpu.arch_perf.width: 48
machdep.cpu.arch_perf.events_number: 7
machdep.cpu.arch_perf.events: 0
machdep.cpu.arch_perf.fixed_number: 3
machdep.cpu.arch_perf.fixed_width: 48
machdep.cpu.cache.linesize: 64
machdep.cpu.cache.L2_associativity: 4
machdep.cpu.cache.size: 256
machdep.cpu.tlb.inst.large: 8
machdep.cpu.tlb.data.small: 64
machdep.cpu.tlb.data.small_level1: 64



Действительно странно. ОС вчера обновил до актуальной.
UFO just landed and posted this here
Для запуска EFI-бинарников (а для Linux это понадобится), мне на современных Mac (после 2011) понадобился rEFInd, так как Boot Menu (которое появляется при удерживании клавиши Option при запуске, до появления яблока) никак не видело Debian. Попробуйте, конечно, развернуть на FAT32-флешку какой-нибудь дистрибутив, причем будет достаточно просто скопировать файлы, так как EFI не требует загрузочных секторов. Только проверьте в терминале с помощью
diskutil list
, чтобы на флешке не было EFI(ESP)-раздела, ибо он, похоже, и мешает стандартному Boot Menu «увидеть» устройство. Потому воспользуйтесь rEFInd, он у меня Debian обнаруживал всегда.
Интересно, сравнение с учетом возможной погрешности помогает в этом случае?
Вообще похоже на ошибку процессора.
А можно вывести результат приведения float к double, когда происходит ошибка?
Т.е. вместо:
float result = 1.0;

if (result != 1.0) {
error++;
...
}

Написать что то вроде:
float result = 1.0;
double resultd = result;

if (resultd != 1.0) {
error++;
std::cout << "(double)1.0f = " << resultd << std::endl;
...
}

Выглядит все как ошибка железа при выполнении команды cvtss2sd.
А можно еще проверить, точно там 0.0 получается. Может cout просто выводит денормализованные значения как 0.
Можно заменить:
std::cout << "(double)1.0f = " << resultd << std::endl;
на
std::cout << "(double)1.0f = " << *(long long*)&resultd << std::endl;
В любом случае других разумных объяснений, кроме ошибки процессора не вижу.
А другие приложения, использующие floating point, не глючат на этой машине?
Глючат, но на удивление очень редко. Иногда bazel падает с тем же аут-оф-баунд при запуске. Иногда комп полностью зависает при сворачивании окна в док (в тот момент когда оно анимировано «уплывает»). Больше никаких проблем не замечал.

Еще интересно — по совету из комментов выше пробовал запускать тест внутри докер контейнера на убунту и ошибка не повторяется (хотя инструкции там те же).
Видимо проблема проявляется только под нагрузкой, возможно когда из-за этого процессор повышает тактовую частоту. Я думаю стоит попробовать ограничить или уменьшить максимальную частоту процессора и посмотреть будет ли повторяться проблема.
Я пробовал грузить сейфмод с последующим отключением всего что не нужно. Кол-во ошибочных выполнений упало до 2х-3х на INT_MAX выборке. Пока не знаю как занизить частоту на маке — попробую разобраться вечером.
Прошу прощения, я не уловил где именно делается сравнение float с 1.0?
Немного удивляет такое, так как прямое сравнение чисел с плавающей точкой — один из древнейших антипаттернов.
Делается в minimal repro на C++. Сравнение делается с константой 1.0, это не результат какого-то выражения, никаких проблемы быть не должно.
eq/neq на float/double — это очень ненадёжная операция; там, где вы делаете сравнение, вполне может быть сравнение float (переменная) и double (константа) — как компилятор решит. Тем не менее, повторяемость должна быть 100%, согласен.
Небольшая заметочка по теме…
К сожалению, у меня на i7 (предыдущего поколения) с build 1.8.0_144 баг не повторяется (ни со строкой, ни с последними примерами). Но и java без изменения не даёт собрать ваш код: по умолчанию все дробные числа имеют тип double, поэтому строчка из последних примеров
float result = 1.0;
собирается только с явным указанием типа (без преобразования самим компилятором, иначе ошибка: «incompatible types: possible lossy conversion from double to float»). То есть так:
float result = 1.0f;
Влияет ли это как-то на генерацию дальнейшего асм-кода ещё не смотрел.
P.S. Об этом даже в вики сказано и есть аналогичные примеры:
float pi = 3.14f; // При использовании типа float требуется указывать суффикс f или F
float anotherPi = (float) 3.14; // Можно привести явно
Если вы про этот код:
bool compare() {
    float val = 1.0;
    return val != 1.0;
}

То автор его на с++ написал. Это не java
Да, но в с++ будет таже проблема. Эти строки делают неявное преобразование double во float. Проверьте ещё такой код:
int a = 16777217 * 1.0f; 
int b = 16777217 * 1.0;
printf("%d %d\n", a, b); //16777216 16777217
Я не знаю (не смотрел сгенерированный ассемблерный код), но я думаю, что вряд ли строка
float val = 1.0;
будет делать преобразование double во float. Мне тоже первой пришла в голову мысль, что надо было бы = 1f, но скорее всего компилятор и так возьмёт 1f, а не будет делать явное преобразование double во float.

Хотя для удобочитаемости — да, стоило б:
bool compare() {
    float val = 1.0f;
    return val != 1.0;
}
Вы неправильно поняли предложенное.
return val != 1.0f;

Но в принципе такое в коде — уже плохо.
Так это ж бред тогда будет — тогда не будет cast'а к double — а эффект, как я понимаю, возникает как раз при наличии cast'а к double (cvtss2sd).

Я бы наоборот, явно это выделил (забыл это сделать в прошлом комменте; хотя, возможно, автор специально так не сделал, потому что тогда какие-то другие эффекты всплывают?):
bool compare() {
    float val = 1.0f;
    return (double)val != 1.0;
}
Надо искать на системе third-part kernel mode драйвер, который не сохраняет контекст перед использованием SSE или других FPU инструкций

А не связано ли это с CVE-2018-3665 и её патчем?

Спасибо за наводку. Тут пишут что «macOS before 10.13.5 is affected» (не знаю насколько это серьезный источник). Вчера обновился до System Version: macOS 10.13.5 (17F77) но проблема, к сожалению, не исчезла.
не воспроизводится.

System Version: macOS 10.13.3 (17D47)
Kernel Version: Darwin 17.4.0
machdep.cpu.brand_string: Intel® Core(TM) i7-4770HQ CPU @ 2.20GHz

Apple LLVM version 9.0.0 (clang-900.0.39.2)
Говорила мне мама, никогда не сравнивай числа с плавающей запятой! Проблема стара как мир формат float, преобразование float<->double не гарантирует равенство результатов, тем более если FPU до сих пор имеют 80-битные регистры от 80387. Я на эту проблему ещё в 1994м что ли нарвался, на Turbo Pascal 6.0, когда var twofloat:single, twodouble:double, twoext:extended присваивались значения 2.0 и потом с ним же сравнивались. Правда, в паскале все числа приводились к extended, а не к double, но смысл тот же. А ошибка тут в дизайне — нафига «expansionFactor» типа float? Если уж надо использовать вещественные числа, используйте нормальный тип, а не легаси. Тем более в случае, когда любое вещественное должно точно сводиться к дроби со знаменателем 8, правильнее использовать константу типа int, равную текущему expansionFactor, умноженному на 8.
Речь ведь совсем не о (float)3 != (double)3. А о том, что (int)(double)f, где float f = 3 регулярно возвращает не 3 (и даже не 2, что ещё хоть как-то можно было бы списать на неточность), а 0.
Да уж, ноль такое преобразование вернуть вообще было не должно. А как по мне, задействовать здесь FPU вообще лишняя работа, с точки зрения исходных данных и результата. Но имеем что имеем.

И в таком случае действительно вариант занизить частоту, может быть такое, что какой-то процесс некорректно отрабатывает на предельных частотах в этом конкретном «камне», например, не выставляется верхний бит экспоненты double в регистре xmm0, из-за чего число в регистре становится «почти нулем», и потом округляется в ноль. Это могло бы объяснить, почему float -> int проходит без проблем — тот бит не задействуется при таком преобразовании (да и вроде бы код float->int использует xmm1, а не xmm0). Возможно, удастся отловить проблему, например, записью в xmm0 статического значения 3.0 из памяти, потом чтение его в два разных места в памяти и побитового сравнения прочитанных результатов между собой и с исходным значением.
в регистре xmm0

какого-то конкретного ядра. Кстати, есть ли возможность в макоси выставить affinity процессу на конкретное ядро? Можно было бы изолировать проблемное ядро таким образом.

Если, как тут пишут проблема в том, что (int)(double)(float)3 возвращает 0, то это, конечно, баг на каком то уровне — процессор или другой чип, драйвер, ОС, etc.


Однако, как уже отметили вопрос тут прежде всего по применимости float чисел к данной задаче.


Дело не в том, что float числа нужно сравнивать с применением epsilon, а в более фундаментальной вещи: зачем вообще в стандартной библиотеке делать реализацию метода, с использованием float, который работает с вещами, которые к float вообще не имеют отношения? Это похоже на хак с использованием каких-то особенностей float, который в редких кейсах приводит к неожиданным проблемам.


С float нужно работать только там, где входные и/или выходные данные float.


В .NET тоже есть похожие реализации с использованием float там, где можно обойтись целочисленными типами (DateTime, TimeSpan — внутри хранят количество тиков в long, часть операций реализована как сложение/вычитание long, а часть — через операции с double).

Видимо потому что множитель дробный. Можно обойтись fixed-point представлением, но это потребует умножения в широких интах, можно эмулировать через сдвиг и сложение, но зачем это всё, если есть флоаты? Только потому что у кого-то железо может глючить?

Видимо потому что множитель дробный.

Понадобился дробный множитель, и, естественно, для этого было выбран тип данных дробных чисел, а, точнее — тип данных с плавающей точкой?


Суть вопроса то в этом и заключается — зачем здесь дробный множитель, если есть в памяти строка из последовательности символов (UTF-16 или UTF-8 для java.lang.String? — кажется, первая), представленных целочисленными байтами, и нужно эти байты перекодировать так, чтобы они представляли ту же самую строку, но уже в другой кодировке?

Судя по всему, автор кода пытался таким образом уберечься от переполнения.


Выражение 2000000000*3 даст в результате 1705032704, в то время как (int)(2000000000*3.0) даст 2147483647...

Кажется найден первоисточник.
Выше я привёл хороший пример и даже угадал, вот наш автор. Он пытался решить проблему java.nio.BufferOverflowException при вызове getBytes(), когда строка имеет определенную длину (от 16777216 символов).
Есть ещё кое-что интересное, но не об этом.
Но это читерство, в реальном продуктовом и тем более библиотечном коде не должно быть:
«2000000000 не можем умножить на 3», т.к. в результате «String.getBytes() does not work on some strings larger than 16MB», давайте тогда приведем все это к double перед умножением.
Сорри, но это уровень студенческой лабораторной работы на тройку.

Уж лучше тогда использовать BigIngteger, хотя это оверхед, да и на момент выявления бага BigInteger в Java еще не было.
Если нужен был Workaround, то можно было б какой то свой Int128 (обертку над парой long) реализовать…
Во всяком случае, если посмотрим исходники JDK 8 по Unsigned-арифметике, там BigInteger вполне используется для борьбы с переполнением.

И наверняка можно было бы все решить проще и элегантнее — только не говорите, что надо было «быстро» — это ж библиотечная функция JDK, а не энтерпрайз-фичи по аджайлу и скраму.
Корень проблемы в том, что maxCharsPerByte() возвращает float. Почему так сделано я не знаю, но видимо это не обсуждается. В результате при умножении результата maxCharsPerByte() на int происходит умножение int на float. При арифметической операции с операндами разного типа оба операнда приводятся к тому типу, который может представить больший диапазон чисел, в данном случае это float. Но т.к. у float разрядность мантиссы 24 бита, а у int разрядность 32 бита, при привидении int к float сохраняются только 24 старших значащих бита, а младшие обнуляются, также если результат умножения получается больше 2^24, то его младшие биты тоже теряются. В итоге результат умножения получается меньше, чем должен быть в точной арифметике.
На мой взгляд нормальным исправлением должно бы было быть изменение возвращаемого типа maxCharsPerByte() на целый, но видимо это невозможно по каким то причинам, поэтому приведение к double вполне корректное решение.
> Корень проблемы в том, что maxCharsPerByte() возвращает float

Тогда совсем печально.
Но позитив тут в том, что тут пример того, почему контракты библиотечных функций должны тщательно продумываться — ибо это влияет не только на продуктовый код, но на на другие библиотечные функции, и просто так это уже не вычистишь, пометив устаревшую функцию как deprecated.
Скоре всего проблема в latency результатов выполнения SSE команд, возможно под нагрузкой она может стать внезапно достаточно большой и в %eax будет сидеть не то что ожидается (в результате vcvttsd2si %eax,%xmm0 согласно тестам latency бывает >12тактов хотя дока утверждает 8), что вполне может быть не документировано. Такого рода «ошибки достаточно часто встречаются в intel CPU и nvidia GPU.Я бы рекомендовал попробовать встроить между этой инструкцией и сравнением %eax один или более NOP. Конечно проблема с конкретным CPU тоже не исключена.
MacBook Pro (15-inch, 2017)
2.8 GHz Intel Core i7
OS: 10.13.4 (17E202)
Memory: 16 GB 2133 MHz LPDDR3
Проблема не воспроизводится. Ни на C++ ни Java.

Проблема может быть конкретно в вашем ноутбуке.
Но проблема весьма занятна. Обычно если 0.1+0.2 != 0.3 то оно всегда так и не меняется от количества вызовов.

Жду продолжения, если найдете причину.
Интересно, существуют ли тесты для процессора (которые user мог бы запустить и проверить, не дефектный ли его процессор)?
И программные способы залочить отдельные ядра (вдруг проблема в каком-то одном из)?
Читал недавно похожую историю, но которая затрагивает все CPU. Тоже сильно удивился. Так что да, примитивы не всегда так уж надежны.
Sign up to leave a comment.

Articles

Change theme settings