2016-07-14 5 views
13

Когда этот расчет с плавающей запятой выполняется в boost::thread, он дает отличный результат, чем при выполнении в std::thread или в основном потоке.Почему этот расчет дает разные результаты в boost :: thread и std :: thread?

void print_number() 
{ 
    double a = 5.66; 
    double b = 0.0000001; 
    double c = 500.4444; 
    double d = 0.13423; 
    double v = std::sin(d) * std::exp(0.4 * a + b)/std::pow(c, 2.3); 

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v); 
} 

Это, кажется, происходит потому, что boost::thread по умолчанию с использованием 53-битной внутренней точности с плавающей точкой математике, в то время как основной поток использует 64-битную точность. Если состояние блока FPU сбрасывается с _fpreset() после создания boost::thread, результат будет таким же, как в основном потоке.

Я использую Embarcadero C++ Builder 10.1 (компилятор bcc32c версии 3.3.1) и Boost 1.55.0. Моя среда - это Windows 7, и я строю для 32-битной цели Windows.

Рабочий пример:

#include <tchar.h> 
#include <thread> 
#include <boost/thread.hpp> 
#include <cstdio> 
#include <cmath> 
#include <cfloat> 

namespace boost { void tss_cleanup_implemented() {} } 

void print_number() 
{ 
    double a = 5.66; 
    double b = 0.0000001; 
    double c = 500.4444; 
    double d = 0.13423; 
    double v = std::sin(d) * std::exp(0.4 * a + b)/std::pow(c, 2.3); 

    // Edit: 
    // Avoiding the undefined behaviour by a reinterpret_cast, as 
    // mentioned in some answers and comments. 
    unsigned long long x; 
    memcpy(&x, &v, sizeof(x)); 

    printf("%llX\n%0.25f\n", x, v); 
} 

void print_number_2() 
{ 
    // Reset FPU precision to default 
    _fpreset(); 
    print_number(); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    print_number(); 

    std::thread t1(&print_number); 
    t1.join(); 

    boost::thread t2(&print_number); 
    t2.join(); 

    boost::thread t3(&print_number_2); 
    t3.join(); 

    getchar(); 
    return 0; 
} 

Выход:

3EAABB3194A6E99A 
0.0000007966525939409087744 
3EAABB3194A6E99A 
0.0000007966525939409087744 
3EAABB3194A6E999 
0.0000007966525939409087488 
3EAABB3194A6E99A 
0.0000007966525939409087744 

Вопрос:

  • Почему это происходит? Разве новый поток не должен наследовать среду с плавающей запятой из родительского потока?
  • Это ошибка в компиляторе или в Boost, или мои ожидания ошибочны?
+3

Полностью несвязанный, но я должен сказать, что ваша презентация вопроса абсолютно * звездная *. – WhozCraig

+0

Просто из любопытства, какую операционную систему вы использовали? – PeterT

+0

@PeterT Я использую Windows 7. –

ответ

2

Разница, по-видимому, заключается в том, что реализация std::thread делает _fpreset(), а boost::thread, очевидно, нет. Если вы измените строку

namespace boost { void tss_cleanup_implemented() { } } 

к (форматированный немного для ясности):

namespace boost 
{ 
    void tss_cleanup_implemented() 
    { 
     _fpreset(); 
    } 
} 

Вы увидите, что все значения в точности то же самое сейчас (3EAABB3194A6E99A). Это говорит мне, что Boost не делает _fpreset(). Этот вызов необходим, потому что некоторые вызовы Windows API испортывают стандартные настройки FPU, которые использует C++ Builder (32 бит), и не устанавливают их обратно на то, что они были (это проблема, с которой вы можете столкнуться и в Delphi).

как std::thread, так и boost:thread Использование API Win32 API для обработки потоков.

Что-то подсказывает мне, что вы ожидали этого уже, поэтому тест с print_number_2(), который делает _fpreset().

+0

Неужели это проблема при использовании 64-разрядной версии Windows? Надеюсь, что они перестанут использовать стек FF x87. – Tim

+0

Я еще не пробовал это на 64 бит, но я бы догадался, что это не так. Я проверю. –

+0

@Tim: нет, это не проблема. То же самое значение в каждом тесте **, но теперь это '3EAABB3194A6E998' **! Я думаю, потому что для Win64 компилятор C++ Builder использует SSE, и у него нет 80-битных промежуточных элементов. –

5

Это: *reinterpret_cast<unsigned long long*>(&v) не определено поведение, как v не unsigned_long_long. Если вы хотите скопировать двоичное представление double на интегральный тип, используйте memcpy(). Обратите внимание, что даже с помощью memcpy(), это реализация определила, как будет выглядеть двоичное представление, но вам гарантировано, что вы сможете «загрузить обратно то, что вы сохранили». Ничего больше AFAIK.

+0

И почему значения различаются, когда двойное значение печатается в десятичной форме? В этом примере я использовал внутреннее представление, чтобы показать, что это не ошибка в 'printf'. –

+2

@ Ville-ValtteriTiittanen Этот ответ указывает только на ошибку в коде, это не значит, что эта ошибка является источником вашей проблемы. Исправление ошибки является обязательным условием для любого дальнейшего расследования, даже если оно, похоже, ничего не «меняет». –

+0

Я этот ответ только указывая на ошибку в коде, тогда это должен был быть комментарий. Ошибка не отвечает за разницу в результатах (в 32-битном C++ Builder «unsigned long long» имеет тот же размер, что и «double»), поэтому это не отвечает на вопрос. –

1

Чтобы избежать, вам нужен лучший компилятор.


Это, кажется, происходит потому, что повышение :: поток по умолчанию с использованием 53-битной внутренней точности с плавающей точкой математике, в то время как основной поток использует 64-битную точность. Если статус блока FPU сбрасывается с помощью _fpreset() после создания boost :: thread, результат будет таким же, как и в основном потоке.

Это безумие. Если ваш компилятор использует другой блок FP (т. Е. X87 против SSE) для разных регионов кода, вы должны записать этот компилятор с самым большим огнем, который вы можете найти.

Выполнение этого кода под g ++ - 6.1 и clang ++ - 3.8 в Linux Mint 17.3 дает одинаковые результаты для каждого типа потока.

#include <thread> 
#include <boost/thread.hpp> 
#include <cstdio> 
#include <cmath> 

void print_number() { 
    double a = 5.66; 
    double b = 0.0000001; 
    double c = 500.4444; 
    double d = 0.13423; 
    double v = std::sin(d) * std::exp(0.4 * a + b)/std::pow(c, 2.3); 

    printf("%llX\n%0.25f\n", *reinterpret_cast<unsigned long long*>(&v), v); 
} 

int main() { 
    print_number(); 

    std::thread t1(&print_number); 
    t1.join(); 

    boost::thread t2(&print_number); 
    t2.join(); 
} 

CXX -std = C++ 14 -O3 -c тест test.c -pthread -lboost_thread -lboost_system

3EAABB3194A6E999
0,0000007966525939409086685

3EAABB3194A6E999
0.0000007966525939409086685

3EAABB3194A6E999
0,0000007966525939409086685


Как @lorro отмечено в его/ее ответа, вы нарушаете правила наложения спектров в reinterpret_cast.

+0

Re: «Чтобы whit [sic], вам нужен лучший компилятор». Поскольку 'std :: thread' работает так, как ожидалось, и' boost :: thread' не делает этого, кажется, более вероятно, что 'boost :: thread' делает что-то особенное, чем компилятор. –

+0

Однако, используя два из трех компиляторов верхнего уровня C++ (третий из которых MSVC, к которому у меня нет доступа), обеспечивают согласованные результаты в реализациях 'std :: thread'. – Tim

+4

Я не понимаю, в чем дело. Да, 'std :: thread' отлично работает везде, насколько это сообщалось. Это 'boost :: thread', который не работает. Здесь ничего нет, что оправдывает обвинение в компиляторе. –

4

Это не разница между вычислениями FPU с точностью 64 и 53 бит, это разница в КРУГЛЫЙ СТОЛ. Единственное различие между этими двумя результатами - это наименее значимый бит ответа. Похоже, что начальный код потока запуска не правильно инициализирует флаги FPU, а режим округления по умолчанию - вниз или прерывается, а не ближе.

Если это так, то это может быть ошибка в boost :: thread. Это может также произойти, если другая библиотека меняет флаги FPU (через _controlfp_s или аналогичную функцию), или если новый поток является частью пула потоков, предыдущий пользователь потока изменил флаги, а пул не сбросил перед повторным использованием потока.

+0

Не использовалось нить. Функции API Win32 имеют тенденцию использовать разные настройки FPU в настройках C++ Builder (и Delphi). Реализации потоков в Win32 используют такие функции API. C++ Builder STL знает это и сбрасывает FPU. Boost не делает. Отсюда и различия. –