Сроки предлагаемых решений
Код, представленный здесь, проверяет производительность 3 предложенных функций в ответе на fearless_fool и решение в ответе по Айксан . Функции модифицируются таким образом, чтобы всегда принимать int
аргументы (const
в int const x
не требуется), но тестовый код использует только тестовые значения в диапазоне, где оба значения x
и y
неотрицательны.
В коде используется набор функций синхронизации, доступных в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файлов timer.c
и timer.h
в src / libsoq подкаталог.
#define NDEBUG 1
#include "timer.h"
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
/* JL: added static to rquotient_xx functions */
/* JL: removed two const qualifiers */
static
int rquotient_dd(int x, int y)
{
return (int)round((double)x / y);
}
/* JL: removed unsigned - added assert */
static
int rquotient_uu(int x, int y)
{
assert(x >= 0 && y > 0);
return (x + y / 2) / y;
}
/* JL: removed unsigned - added assert */
static
int rquotient_su(int x, int y)
{
assert(y > 0);
if (x > 0)
return (x + y / 2) / y;
else
return (x - y / 2) / y;
}
static
int rquotient_ss(int x, int y)
{
if ((x ^ y) > 0)
return (x + y / 2) / y;
else
return (x - y / 2) / y;
}
typedef int (*Divider)(int x, int y);
static void test_harness(const char *tag, Divider function)
{
Clock clk;
unsigned long long accumulator = 0;
clk_init(&clk);
clk_start(&clk);
for (int i = 1; i < INT_MAX / 1024; i += 13)
{
int max_div = i / 4;
if (max_div == 0)
max_div = 1;
for (int j = 1; j < max_div; j += 15)
accumulator += (*function)(i, j);
}
clk_stop(&clk);
char buffer[32];
printf("%s: %10s (%llu)\n", tag, clk_elapsed_us(&clk, buffer, sizeof(buffer)), accumulator);
}
int main(void)
{
for (int i = 0; i < 10; i++)
{
test_harness("rquotient_uu", rquotient_uu);
test_harness("rquotient_su", rquotient_su);
test_harness("rquotient_ss", rquotient_ss);
test_harness("rquotient_dd", rquotient_dd);
}
return 0;
}
Использование accumulator
служит двум важным целям. Во-первых, он проверяет, что разные вычисления дают одинаковые результаты. Во-вторых, это гарантирует, что компилятор не может оптимизировать циклы - накопленное значение должно быть напечатано. Отрадно видеть, что накопленное значение одинаково во всех тестах. Нечетные константы (INT_MAX / 1024
, 13
, 15
) являются предполагаемыми значениями, которые дают разумное время на тестовой машине - они означают, что тесты охватывают довольно много значений, не принимая неоправданно длинных времен.
Результаты тестов производительности
Я провел тесты на MacBook Pro (15 дюймов, 2017 г. - с 2,9 ГГц чипом Intel Core i7 и 16 ГБ 2133 МГц ОЗУ LPDDR3) под управлением MacOS 10.14.6 Mojave, скомпилирован с (самодельный) G CC 9.2.0 и набор инструментов Xcode 11.3.1.
$ gcc -O3 -g -I./inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
> iround53.c -o iround53 -L./lib -lsoq
$
Один набор результатов синхронизации был:
rquotient_uu: 6.272698 (286795780245)
rquotient_su: 6.257373 (286795780245)
rquotient_ss: 6.221263 (286795780245)
rquotient_dd: 10.956196 (286795780245)
rquotient_uu: 6.247602 (286795780245)
rquotient_su: 6.289057 (286795780245)
rquotient_ss: 6.258776 (286795780245)
rquotient_dd: 10.878083 (286795780245)
rquotient_uu: 6.256511 (286795780245)
rquotient_su: 6.286257 (286795780245)
rquotient_ss: 6.323997 (286795780245)
rquotient_dd: 11.055200 (286795780245)
rquotient_uu: 6.256689 (286795780245)
rquotient_su: 6.302265 (286795780245)
rquotient_ss: 6.296409 (286795780245)
rquotient_dd: 10.943110 (286795780245)
rquotient_uu: 6.239497 (286795780245)
rquotient_su: 6.238150 (286795780245)
rquotient_ss: 6.195744 (286795780245)
rquotient_dd: 10.975971 (286795780245)
rquotient_uu: 6.252275 (286795780245)
rquotient_su: 6.218718 (286795780245)
rquotient_ss: 6.241050 (286795780245)
rquotient_dd: 10.986962 (286795780245)
rquotient_uu: 6.254244 (286795780245)
rquotient_su: 6.213412 (286795780245)
rquotient_ss: 6.280628 (286795780245)
rquotient_dd: 10.963290 (286795780245)
rquotient_uu: 6.237975 (286795780245)
rquotient_su: 6.278504 (286795780245)
rquotient_ss: 6.286199 (286795780245)
rquotient_dd: 10.984483 (286795780245)
rquotient_uu: 6.219504 (286795780245)
rquotient_su: 6.208329 (286795780245)
rquotient_ss: 6.251772 (286795780245)
rquotient_dd: 10.983716 (286795780245)
rquotient_uu: 6.369181 (286795780245)
rquotient_su: 6.362766 (286795780245)
rquotient_ss: 6.299449 (286795780245)
rquotient_dd: 11.028050 (286795780245)
При анализе среднее значение и примерное стандартное отклонение для различных функций:
Function Count Mean Standard deviation
rquotient_uu 10 6.260618 0.040679 (sample)
rquotient_su 10 6.265483 0.048249 (sample)
rquotient_ss 10 6.265529 0.039216 (sample)
rquotient_dd 10 10.975506 0.047673 (sample)
Не требуется больших статистических знаний, чтобы увидеть, что по существу нет разницы в производительности между тремя «целочисленными» функциями, потому что разница между три средних - это намного меньше, чем одно стандартное отклонение (и, чтобы быть значительным, оно должно быть больше, чем одно стандартное отклонение). Также не нужно много умения, чтобы заметить, что преобразование в double
, деление, округление и обратное преобразование в целое число занимает почти вдвое больше времени, чем целочисленные версии. В прошлые (давние) времена, целочисленные значения против расхождений с плавающей точкой могли быть намного больше. В вычислениях и накоплениях l oop имеется небольшой объем накладных расходов; это увеличило бы несоответствие между вычислениями целых чисел и вычислений с плавающей точкой.
На компьютере, на котором выполнялся тест, в фоновом режиме были открыты различные программы, но не воспроизводилось видео, браузер отображал переполнение стека, а не рекламу. загруженные страницы, и я возился с мобильным телефоном, пока тест выполнялся на ноутбуке. Одна попытка запуска теста, во время которой я переключался между страницами в браузере, показала гораздо большее количество ошибок c (больше времени, когда я использовал браузер, даже если это многоядерный компьютер).
Другие тесты с условием if ((x ^ y) > 0)
, исправленным на if ((x ^ y) >= 0)
, дали немного другие результаты синхронизации (но то же значение для accumulator
):
rquotient_su 10 6.272791 0.037206
rquotient_dd 10 9.396147 0.047195
rquotient_uu 10 6.293301 0.056585
rquotient_ss 10 6.271035 0.052786
rquotient_su 10 6.187112 0.131749
rquotient_dd 10 9.100924 0.064599
rquotient_uu 10 6.127121 0.092406
rquotient_ss 10 6.203070 0.219747
rquotient_su 10 6.171390 0.133949
rquotient_dd 10 9.195283 0.124936
rquotient_uu 10 6.214054 0.177490
rquotient_ss 10 6.166569 0.138124
Разница в производительности для арифметики с плавающей точкой c не совсем так явно, но все же окончательно в пользу целочисленной арифметики c. В частности, последние из этих тестов предполагают, что во время выполнения тестов на компьютере была какая-то другая активность - хотя это не я смотрел на веб-страницы или что-то в этом роде.
Использование -ffast-math
Ayxan спросил:
Интересно, если бы -ffast-math
имел бы значение.
Я перекомпилировал с дополнительным вариант, и это действительно имеет значение. Обратите внимание, что исходный код был скомпилирован с -O3
- он был оптимизирован. Тем не менее, необработанные данные из прогона с -ffast-math
были:
rquotient_uu: 6.162182 (286795780245)
rquotient_su: 6.068469 (286795780245)
rquotient_ss: 6.041566 (286795780245)
rquotient_dd: 4.568538 (286795780245)
rquotient_uu: 6.143200 (286795780245)
rquotient_su: 6.071906 (286795780245)
rquotient_ss: 6.063543 (286795780245)
rquotient_dd: 4.543419 (286795780245)
rquotient_uu: 6.115283 (286795780245)
rquotient_su: 6.083157 (286795780245)
rquotient_ss: 6.063975 (286795780245)
rquotient_dd: 4.536071 (286795780245)
rquotient_uu: 6.078680 (286795780245)
rquotient_su: 6.072075 (286795780245)
rquotient_ss: 6.104850 (286795780245)
rquotient_dd: 4.585272 (286795780245)
rquotient_uu: 6.084941 (286795780245)
rquotient_su: 6.080311 (286795780245)
rquotient_ss: 6.069046 (286795780245)
rquotient_dd: 4.563945 (286795780245)
rquotient_uu: 6.075380 (286795780245)
rquotient_su: 6.236980 (286795780245)
rquotient_ss: 6.210127 (286795780245)
rquotient_dd: 4.787269 (286795780245)
rquotient_uu: 6.406603 (286795780245)
rquotient_su: 6.378812 (286795780245)
rquotient_ss: 6.194098 (286795780245)
rquotient_dd: 4.589568 (286795780245)
rquotient_uu: 6.243652 (286795780245)
rquotient_su: 6.132142 (286795780245)
rquotient_ss: 6.079181 (286795780245)
rquotient_dd: 4.595330 (286795780245)
rquotient_uu: 6.070584 (286795780245)
rquotient_su: 6.081373 (286795780245)
rquotient_ss: 6.075867 (286795780245)
rquotient_dd: 4.558105 (286795780245)
rquotient_uu: 6.106258 (286795780245)
rquotient_su: 6.091108 (286795780245)
rquotient_ss: 6.128787 (286795780245)
rquotient_dd: 4.553061 (286795780245)
И статистика из этого:
rquotient_su 10 6.129633 0.101331
rquotient_dd 10 4.588058 0.072669
rquotient_uu 10 6.148676 0.104937
rquotient_ss 10 6.103104 0.057498
Не нужно быть статистическим гением, чтобы заметить, что это показывает, что -ffast-math
альтернатива с плавающей запятой теперь лучше, чем целочисленная версия - аналогично тому, как целое число было лучше, чем с плавающей запятой без дополнительной опции компилятора .
Еще один набор статистики с -ffast-math
. Они показывают меньшие отклонения (стандартные отклонения), но тот же общий результат.
rquotient_su 10 6.060705 0.024372
rquotient_dd 10 4.543576 0.014742
rquotient_uu 10 6.057718 0.026419
rquotient_ss 10 6.061652 0.034652
Для 32-разрядных целых чисел может показаться, что при -ffast-math
код, использующий double
, может быть быстрее, чем код с использованием только целых чисел.
Если диапазон был изменен с 32-разрядных целых чисел на 64-разрядные целые числа, то 64-разрядные двойные числа не смогут точно представлять все целочисленные значения. В этот момент, если разделяемые числа достаточно велики, вы можете начать обнаруживать ошибки точности (результаты накопителя могут отличаться). 64-битный дубль фактически имеет 53 бита для представления мантиссы, поэтому, если число битов в целых числах было больше, точность падает.
Тестирование производительности затруднительно. YMMV!
Действительно, было бы безопаснее сказать "Ваше Милеж" WILL Vary ".