Большинство других ответов здесь просто пытается устранить цикл наверху.
Только ответ deft_code добирается до сути того, что вероятно настоящая проблема - что преобразование плавающей точки к целым числам является очень дорогим на x86 процессоре. решение deft_code правильно, хотя он не дает цитаты или объяснения.
Вот источник приема с некоторым объяснением и также версиями, характерными для того, хотите ли Вы окружить, вниз, или к нулю: Знают, что Ваш FPU
Извините предоставляет ссылку, но действительно что-либо записанное здесь, за исключением репродуцирования, что превосходная статья, не собирается ясно давать понять вещи.
Если Вы имеете очень большие массивы (больше, чем некоторые МБ - размер кэша ЦП), время Ваш код и видите, какова пропускная способность. Вы, вероятно, насыщаете шину памяти, не единицу FP. Ищите максимальную теоретическую пропускную способность для своего ЦП и посмотрите, как близко к нему Вы.
, Если Вы ограничиваетесь шиной памяти, дополнительные потоки просто сделают ее хуже. Вам нужны лучшие аппаратные средства (например, более быстрая память, другой ЦП, другая материнская плата).
<час> В ответ на комментарий Larry Gritz...
Вы корректны: FPU является главным узким местом (и использование приема xs_CRoundToInt позволяет очень близко подходить к насыщению шины памяти).
Вот некоторые результаты испытаний для процессора (Q6600) Core 2. Теоретическая пропускная способность оперативной памяти для этой машины составляет 3,2 ГБ/с (L1, и пропускная способность L2 намного выше). Код был скомпилирован с Visual Studio 2008. Подобные результаты для 32-разрядного и 64-разрядного, и с/O2 или оптимизацией Вола/.
WRITING ONLY... 1866359 ticks with 33554432 array elements (33554432 touched). Bandwidth: 1.91793 GB/s 154749 ticks with 262144 array elements (33554432 touched). Bandwidth: 23.1313 GB/s 108816 ticks with 8192 array elements (33554432 touched). Bandwidth: 32.8954 GB/s USING CASTING... 5236122 ticks with 33554432 array elements (33554432 touched). Bandwidth: 0.683625 GB/s 2014309 ticks with 262144 array elements (33554432 touched). Bandwidth: 1.77706 GB/s 1967345 ticks with 8192 array elements (33554432 touched). Bandwidth: 1.81948 GB/s USING xs_CRoundToInt... 1490583 ticks with 33554432 array elements (33554432 touched). Bandwidth: 2.40144 GB/s 1079530 ticks with 262144 array elements (33554432 touched). Bandwidth: 3.31584 GB/s 1008407 ticks with 8192 array elements (33554432 touched). Bandwidth: 3.5497 GB/s
(Windows) исходный код:
// floatToIntTime.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include <iostream>
using namespace std;
double const _xs_doublemagic = double(6755399441055744.0);
inline int xs_CRoundToInt(double val, double dmr=_xs_doublemagic) {
val = val + dmr;
return ((int*)&val)[0];
}
static size_t const N = 256*1024*1024/sizeof(double);
int I[N];
double F[N];
static size_t const L1CACHE = 128*1024/sizeof(double);
static size_t const L2CACHE = 4*1024*1024/sizeof(double);
static size_t const Sz[] = {N, L2CACHE/2, L1CACHE/2};
static size_t const NIter[] = {1, N/(L2CACHE/2), N/(L1CACHE/2)};
int main(int argc, char *argv[])
{
__int64 freq;
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
cout << "WRITING ONLY..." << endl;
for (int t=0; t<3; t++) {
__int64 t0,t1;
QueryPerformanceCounter((LARGE_INTEGER*)&t0);
size_t const niter = NIter[t];
size_t const sz = Sz[t];
for (size_t i=0; i<niter; i++) {
for (size_t n=0; n<sz; n++) {
I[n] = 13;
}
}
QueryPerformanceCounter((LARGE_INTEGER*)&t1);
double bandwidth = 8*niter*sz / (((double)(t1-t0))/freq) / 1024/1024/1024;
cout << " " << (t1-t0) << " ticks with " << sz
<< " array elements (" << niter*sz << " touched). "
<< "Bandwidth: " << bandwidth << " GB/s" << endl;
}
cout << "USING CASTING..." << endl;
for (int t=0; t<3; t++) {
__int64 t0,t1;
QueryPerformanceCounter((LARGE_INTEGER*)&t0);
size_t const niter = NIter[t];
size_t const sz = Sz[t];
for (size_t i=0; i<niter; i++) {
for (size_t n=0; n<sz; n++) {
I[n] = (int)F[n];
}
}
QueryPerformanceCounter((LARGE_INTEGER*)&t1);
double bandwidth = 8*niter*sz / (((double)(t1-t0))/freq) / 1024/1024/1024;
cout << " " << (t1-t0) << " ticks with " << sz
<< " array elements (" << niter*sz << " touched). "
<< "Bandwidth: " << bandwidth << " GB/s" << endl;
}
cout << "USING xs_CRoundToInt..." << endl;
for (int t=0; t<3; t++) {
__int64 t0,t1;
QueryPerformanceCounter((LARGE_INTEGER*)&t0);
size_t const niter = NIter[t];
size_t const sz = Sz[t];
for (size_t i=0; i<niter; i++) {
for (size_t n=0; n<sz; n++) {
I[n] = xs_CRoundToInt(F[n]);
}
}
QueryPerformanceCounter((LARGE_INTEGER*)&t1);
double bandwidth = 8*niter*sz / (((double)(t1-t0))/freq) / 1024/1024/1024;
cout << " " << (t1-t0) << " ticks with " << sz
<< " array elements (" << niter*sz << " touched). "
<< "Bandwidth: " << bandwidth << " GB/s" << endl;
}
return 0;
}
Я удивлен Вашим результатом. Какой компилятор Вы используете? Вы компилируете с оптимизацией, превращенной полностью? Вы подтвердили использование valgrind и Kcachegrind, который это - то, где узкое место? Какой процессор Вы используете? На что похож ассемблерный код?
само преобразование должно быть скомпилировано в единственную инструкцию. Хороший оптимизирующий компилятор должен развернуть цикл так, чтобы несколько преобразований были сделаны на тест-и-ответвление. Если этого не происходит, Вы можете разворачивать цикл рукой :
for(int i = 0; i < HUGE_NUMBER-3; i += 4) {
int_array[i] = float_array[i];
int_array[i+1] = float_array[i+1];
int_array[i+2] = float_array[i+2];
int_array[i+3] = float_array[i+3];
}
for(; i < HUGE_NUMBER; i++)
int_array[i] = float_array[i];
, Если Ваш компилятор действительно вызывает жалость, Вы, возможно, должны были бы помочь ему с общими подвыражениями, например,
int *ip = int_array+i;
float *fp = float_array+i;
ip[0] = fp[0];
ip[1] = fp[1];
ip[2] = fp[2];
ip[3] = fp[3];
Действительно сообщают с большим количеством информации!
На Intel Ваш лучший выбор является встроенными вызовами SSE2.
большинство компиляторов C генерирует вызовы к _ftol или чему-то для каждого плавания к международному преобразованию. помещение уменьшенного переключателя соответствия с плавающей точкой (как fp:fast) могло бы помочь - ЕСЛИ Вы понимаете И принимаете другие эффекты этого переключателя. кроме этого, помещает вещь в трудный блок или sse внутренний цикл, ЕСЛИ Вы в порядке И понимаете другое поведение округления. для больших циклов как Ваш пример необходимо записать функцию, которая настраивает управляющие слова с плавающей точкой однажды и затем делает объем, округляющийся с только fistp инструкции, и затем сбрасывает управляющее слово - ЕСЛИ Вы соглашаетесь с x86 только путь выполнения кода, но по крайней мере Вы не измените округление. прочитайте на fld и fistp fpu инструкции и fpu управляющее слово.
См. эту статью Intel для ускорения целочисленных преобразований:
http://software.intel.com/en-us/articles/latency-of-floating-point-to-integer-conversions/
По данным Microsoft,/QIfist параметр компилятора удерживается от использования в VS 2005, потому что целочисленное преобразование было ускорено. Они забыли говорить, как это было ускорено, но рассмотрение списка дизассемблирования могло бы дать ключ к разгадке.
http://msdn.microsoft.com/en-us/library/z8dh4h17 (по сравнению с 80) .aspx
Какой компилятор Вы используете? В более свежих компиляторах C/C++ Microsoft существует опция под C/C++-> Генерация кода-> модель С плавающей точкой, которая имеет опции: быстрый, точный, строгий. Я думаю точный, значение по умолчанию и работает путем эмуляции операций FP в некоторой степени. Если Вы используете компилятор MS, как эта опция установлена? Это помогает установить его на "быстро"? В любом случае, на что похоже дизассемблирование?
, Поскольку thirtyseven сказал выше, ЦП может преобразовать float<->int
в по существу одной инструкции, и это не добирается немного быстрее, чем тот (за исключением операции SIMD).
Также примечание, что современные центральные процессоры используют ту же единицу FP и для единственного (32 бита) и для двойные числа FP (на 64 бита), поэтому если Вы не пытаетесь сохранить память, хранящую много плаваний, нет действительно никакой причины способствовать float
по дважды.
Вы смогли загружать все целые числа в модуль SSE Вашего процессора с помощью некоторого волшебного ассемблерного кода, затем сделайте эквивалентный код для устанавливания значений к ints, затем считайте их как плавания. Я не уверен, что это было бы немного быстрее все же. Я не гуру SSE, таким образом, я не знаю, как сделать это. Возможно, кто-то еще может вмешаться.
Ключ должен избежать _ftol () функция, которая является напрасно медленной. Ваш лучший выбор для длинных списков данных как это состоит в том, чтобы использовать инструкцию SSE2 cvtps2dq для преобразования двух упакованных плаваний в два, упаковал int64s. Сделайте это дважды (получение четырех int64s через два регистра SSE), и можно переставить их вместе для получения четырех int32s (теряющий лучшие 32 бита каждого результата преобразования). Вам не нужен блок, чтобы сделать это; MSVC выставляет компилятор intrinsics соответствующим инструкциям - _mm_cvtpd_epi32 () , если моя память служит мне правильно.
, Если Вы делаете это, очень важно, чтобы Ваше плавание и интервал выстроили быть 16 байтов, выровненных так, чтобы загрузка и хранение SSE2 intrinsics могла работать с максимальной производительностью. Кроме того, я рекомендую Вам конвейер программного обеспечения немного и обрабатываю шестнадцать плавания сразу в каждом цикле, например, (предполагающий, что "функции" здесь являются на самом деле вызовами к компилятору intrinsics):
for(int i = 0; i < HUGE_NUMBER; i+=16)
{
//int_array[i] = float_array[i];
__m128 a = sse_load4(float_array+i+0);
__m128 b = sse_load4(float_array+i+4);
__m128 c = sse_load4(float_array+i+8);
__m128 d = sse_load4(float_array+i+12);
a = sse_convert4(a);
b = sse_convert4(b);
c = sse_convert4(c);
d = sse_convert4(d);
sse_write4(int_array+i+0, a);
sse_write4(int_array+i+4, b);
sse_write4(int_array+i+8, c);
sse_write4(int_array+i+12, d);
}
причина этого состоит в том, что инструкции SSE имеют длинную задержку, поэтому если Вы будете следовать за загрузкой в xmm0 сразу с зависимой операцией на xmm0 затем, то у Вас будет останов. Наличие нескольких регистров "в полете" сразу скрывает задержку немного. (Теоретически волшебный всезнающий компилятор мог исказить свой путь вокруг этой проблемы, но на практике это не делает.)
В случае неудачи амулет SSE можно предоставить/QIfist опцию к MSVC, который заставит это выпускать единственный кулак кода операции вместо вызова к _ftol; это означает, что будет просто использовать, какой бы ни округление режима, оказывается, установлено в ЦП, не удостоверяясь, что это - определенный усеченный op C ANSI. В документах Microsoft говорится, что/QIfist удерживается от использования, потому что их код с плавающей точкой быстр теперь, но дизассемблер покажет Вам, что это невыровнено оптимистично. Даже/fp:fast просто заканчивается к вызову к _ftol_sse2, который, хотя быстрее, чем вопиющий _ftol все еще вызов функции, сопровождаемый скрытым SSE op, и таким образом излишне замедляется.
я предполагаю, что Вы находитесь на x86 дуге, между прочим - если Вы находитесь на PPC существуют эквивалентные операции VMX, или можно использовать прием magic-number-multiply, упомянутый выше сопровождаемый vsel (для каширования битов немантиссы) и выровненное хранилище.
Действительно ли время является достаточно большим, что оно перевешивает стоимость запуска нескольких потоков?
Принятие у Вас есть многоядерный процессор или несколько процессоров на Вашем поле, которое Вы могли использовать в своих интересах, это будет тривиальной задачей параллелизировать через несколько потоков.
Существует инструкция FISTTP в системе команд SSE3, которая делает то, что Вы хотите, но относительно того, могла ли она быть использована и привести к более быстрым результатам, чем libc, я понятия не имею.
inline int float2int( double d )
{
union Cast
{
double d;
long l;
};
volatile Cast c;
c.d = d + 6755399441055744.0;
return c.l;
}
// this is the same thing but it's
// not always optimizer safe
inline int float2int( double d )
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
for(int i = 0; i < HUGE_NUMBER; i++)
int_array[i] = float2int(float_array[i]);
двойной параметр не является ошибкой! Существует способ сделать этот прием с плаваниями непосредственно, но это получает ужасную попытку покрыть все угловые случаи. В его текущей форме эта функция будет вокруг плавания ближайшее целое число, если Вы захотите усечение, вместо этого используют 6755399441055743.5 (0.5 меньше).
я запустил некоторые тесты на различных способах сделать преобразование плавания к интервалу. Короткий ответ должен предположить, что Ваш клиент имеет SSE2-способные центральные процессоры и установил/arch:SSE2 флаг компилятора. Это позволит компилятору использовать скаляр SSE инструкции, которые являются дважды с такой скоростью, как даже метод магического числа.
Иначе, если у Вас есть длинные строки плаваний для шлифования, используют упакованную операцию в секунду SSE2.
В Visual C++ 2008 компилятор генерирует вызовы SSE2 отдельно, если Вы делаете сборку конечных версий с истраченными опциями оптимизации и смотрите на дизассемблирование (хотя некоторые условия нужно соблюдать, игра вокруг с Вашим кодом).