c++ практическа изчислителна сложност на ‹cmath› SQRT()

Каква е разликата в циклите на процесора (или, по същество, в "скоростта") между

 x /= y;

и

 #include <cmath>
 x = sqrt(y);

РЕДАКТИРАНЕ: Знам, че операциите не са еквивалентни, просто произволно предлагам x /= y като еталон за x = sqrt(y)


person Matt Munson    schedule 30.07.2011    source източник
comment
Какво....? Как са свързани тези?   -  person duffymo    schedule 30.07.2011
comment
Много зависи от компилатора, конфигурацията и целевия процесор.   -  person Yakov Galka    schedule 30.07.2011
comment
а? Това са съвсем различни операции. Каква е разликата между printf и new? Във всеки случай просто сравнете монтажа!   -  person Kerrek SB    schedule 30.07.2011
comment
Предполагам, че производителността на sqrt зависи поне отчасти от стойността на x   -  person Cameron    schedule 30.07.2011
comment
Дори с y, тези фрагменти все още са напълно различни.   -  person Cameron    schedule 30.07.2011
comment
Въпреки че сравняването на две различни операции може да звучи странно, определено е възможно (дори ако зависи от платформата и е доста трудно да се направи правилно). Познаването на приблизителната относителна скорост на основните операции с плаваща запетая е важно, когато се правят оптимизации на ниско ниво. Понякога можете да решите същия проблем, например (изкуствен пример) или като умножите 4 пъти и разделите 3 пъти, или умножите 2 пъти и извлечете корен квадратен 2 пъти.   -  person Suma    schedule 30.07.2011
comment
@Mat отправна точка. Ако имам опит със скоростта на едно нещо (защото е повсеместно), тогава мога да оценя скоростта на друго нещо, когато знам скоростта му спрямо първото.   -  person Matt Munson    schedule 30.07.2011
comment
@Suma: Не можете да сравните двете, освен ако не ви е дадена конкретна реализация на разделението и sqrt. Дори ако и двата са преведени в x84 машинен код, тяхната производителност може да е различна с различните процесори (напр. стар срещу нов).   -  person Yakov Galka    schedule 30.07.2011
comment
Момчета, въпреки че не е напълно ясно, вярвам, че това е истински въпрос. @Matt: на по-малко мощни системи, които нямат специален хардуер, sqrt обикновено е x10 по-бавен от div. На всеки хардуер от това десетилетие те са много близки и доста често се обединяват заедно в подобна производителност с плаваща запетая. Можете да потърсите времена на процесора на вашия конкретен процесор, за да получите по-добро усещане.   -  person Michael Dorgan    schedule 30.07.2011
comment
Тук friweb.hu/instlatx64 можете да намерите измерени времена на всички x86 инструкции (ns и тикове). напр. за Core 2 Duo E6700 латентността (L) на операцията x87 sqrt е 29 отметки за 32-битов float; 58 тикчета за 64-bit double и 69 тикчета за 80-bit long double; Времето SSE/SSE2 за 32/64-битова опаковка с плаваща запетая е същото (29 и 58 отметки). За F.P. Разделяне: 32bit=18clock; 64bit=32clock; 80bit=38 тикчета; 32/64bit същото за x87 и SSE/SSE2. Във вашата операция има зареждане и съхраняване на стойност, която трябва да се отчете допълнително. Това трябва да е отговорът, но някои затвориха това добро Q.   -  person osgx    schedule 30.07.2011
comment
@ybun Съжалявам, ако въпросът е двусмислен, но просто се опитвам да получа обща представа за това колко sqrt() може да забави моя код.   -  person Matt Munson    schedule 30.07.2011
comment
Последният коментар наистина няма смисъл. Ако имате нужда от квадратен корен, ще трябва да платите разходите за изчисляването му. sqrt не забавя вашия код. Дали sqrt ще бъде бърз или не зависи от вашия компилатор и вашата платформа.   -  person Mat    schedule 30.07.2011
comment
@Mat Но в някои ситуации може да се избегне изчисляването на квадратен корен.   -  person Matt Munson    schedule 30.07.2011
comment
Това е смешен въпрос - няма никакви данни и основание за него. Много по-вероятно е останалата част от кода на Мат да го потопи, а не квадратен корен. Бих гласувал за затваряне.   -  person duffymo    schedule 31.07.2011


Отговори (3)


Отговорът на вашия въпрос зависи от вашата целева платформа. Ако приемем, че използвате най-често срещания x86 процесор, мога да ви дам тази връзка http://instlatx64.atw.hu/ Това е колекция от измерена латентност на инструкции (Колко време ще отнеме на процесора, за да получи резултат, след като има аргумент) и как те са конвейерно подредени за много x86 и x86_64 процесори. Ако целта ви не е x86, можете да опитате сами да измерите разходите или да се консултирате с документацията на вашия процесор.

Първо трябва да получите разглобяващ инструмент на вашите операции (от компилатора, напр. gcc: gcc file.c -O3 -S -o file.asm или чрез разглобяване на компилиран двоичен файл, напр. с помощта на дебъгер). Не забравяйте, че във вашата операция има зареждане и съхраняване на стойност, която трябва да се отчете допълнително.

Ето два примера от friweb.hu:

За Core 2 Duo E6700 латентност (L) на SQRT (и двете версии x87, SSE и SSE2)

  • 29 отметки за 32-битов float; 58 отметки за 64-битов двойно; 69 отметки за 80-битово дълго двойно;

на DIVIDE (на числа с плаваща запетая):

  • 18 отметки за 32-битов; 32 отметки за 64-битов; 38 отметки за 80-битов

За по-новите процесори цената е по-малка и е почти еднаква за DIV и за SQRT, напр. за Sandy Bridge Intel CPU:

SQRT с плаваща запетая е

  • 14 отметки за 32 бита; 21 отметки за 64 бита; 24 отметки за 80 бита

DIVIDE с плаваща запетая е

  • 14 отметки за 32 бита; 22 отметки за 64 бита; 24 отметки за 80 бита

SQRT дори по-бързо за 32 бита.

Така че: За по-стари процесори sqrt сам по себе си е 30-50% по-бавен от fdiv; За по-нов процесор цената е същата. За по-новите процесори цената на двете операции става по-ниска, отколкото за по-старите процесори; За по-дълъг плаващ формат ви трябва повече време; напр. за 64-bit ви трябва 2 пъти повече време, отколкото за 32-bit; но 80-битовият е евтин в сравнение с 64-битовия.

Освен това по-новите процесори имат векторни операции (SSE, SSE2, AVX) със същата скорост като скаларната (x87). Векторите са от 2-4 еднотипни данни. Ако можете да подравните своя цикъл, за да работите върху няколко FP стойности с една и съща операция, ще получите повече производителност от процесора.

person osgx    schedule 30.07.2011
comment
Сигурен съм, че се подразбира, но предполагам, че ‹cmath› sqrt се възползва от тези оптимизации на процесора? - person Matt Munson; 30.07.2011
comment
C++ cmath използва същия sqrt() като C версията на math.h. Но вътрешно sqrt() може да има малко повече от само FSQRT asm код, напр. обработка на грешки. Също така, понякога gcc няма да извика sqrt() на мястото на извикването, така че излишните разходи за извикване на функция ще бъдат тук. Трябва да проверите дизасемблера на ВАШАТА функция и да го grep за машинни кодове със sqrt в техните имена. Опитайте също опция -ffast-math. - person osgx; 31.07.2011

Ако функцията за квадратен корен не е реализирана в специален хардуер или софтуер, повечето библиотечни функции биха я изчислили, използвайки метода на Нютон, който се сближава квадратично.

Методът на Нютон е итеративен метод: правите първоначално предположение, изчислявате резултат от пробата и го използвате за следващото предположение. Повтаряте, докато смятате, че имате резултат, който е „достатъчно близък“. Случва се така, че можете да докажете колко итерации са ви необходими с квадратен корен. Всеки път през цикъла получавате още две цифри точност, така че повечето реализации ще се сближат до границата на прецизност от двойни за 8-9 цикъла.

Ако прочетете това внимателно, ще видите, че итеративният метод на Нютон прави две изваждания , едно умножение и едно деление на итерация.

person duffymo    schedule 30.07.2011
comment
Бихте ли обяснили, че се сближава квадратично? - person Kerrek SB; 30.07.2011
comment
@duffymo Така че ‹cmath› внедрява SQRT, използвайки метода на Newtons, или се възползва от оптимизациите на процесора, които други споменаха? - person Matt Munson; 30.07.2011
comment
Този въпрос е числени методи. Тук му е мястото. @Matt, не знам за конкретното ти изпълнение. Вашият C++ компилатор може да вмъкне инструкции за машинно оптимизирана версия. - person duffymo; 30.07.2011
comment
@duffy о, уау, така че това е ~8 * (2 суб., 1 мулти. и едно разделение). Освен ако процесорът не се оптимизира значително отвъд това, това е повече изчисление, отколкото се надявах. Благодаря. - person Matt Munson; 31.07.2011
comment
Държиш се глупаво. Това е микрооптимизация. Имате ли някакви данни, които да предполагат, че по-голямата част от времето на вашето приложение се изразходва за изчисляване на корен квадратен? Ако не, забравете за него, докато не го направите. Съмнявам се, че имате някакви данни, които да мотивират този въпрос. - person duffymo; 31.07.2011
comment
@duffy Добре, квадратните корени се изчисляват рекурсивно. Приложението е в невронни мрежи, така че програмата просто изпълнява един и същ цикъл многократно, подавайки нов вход към мрежата при всяка итерация. Разбира се, това е микрооптимизация, но ако микрооптимизирам всеки аспект на алгоритъма, което е практично предвид относителната краткост на кода, мисля, че може да доведе до ползи, които си заслужават. - person Matt Munson; 31.07.2011
comment
Мисля, че там е ключът. Измерете го - профилирайте своя код и бъдете сигурни. Може да се изненадате от резултата. - person duffymo; 31.07.2011
comment
@duffy Какъв е най-добрият начин да направите това? Ако стартирам програмата достатъчно дълго, мога ли просто да измеря колко итерации изпълнява за x време? Или ще ми трябва някакъв конкретен вид софтуер, за да измеря действително изчислителното време? - person Matt Munson; 31.07.2011
comment
@KerrekSB Квадратична конвергенция означава, грубо, че всяка итерация броят на цифрите с точност се удвоява. Например грешка при итерация 1 има 0.1, итерация 2 има грешка 0.01, итерация 3 има грешка 0.001, итерация 4 има грешка 0.00001, итерация 5 има грешка 0.000000001. - person Nick Alger; 27.06.2016

Като общо правило: както делението с плаваща запетая, така и квадратният корен се считат за бавна операция (в сравнение с бързите като събиране или умножение). Може да се очаква квадратният корен да бъде приблизително същата скорост или малко по-бавен (т.е. приблизително 1x - 2x по-ниска производителност) в сравнение с деление. напр. на Pentium Pro

Делението и квадратният корен имат латентност съответно от 18 до 36 и 29 до 69 цикъла

За да получите по-точен отговор, трябва да се заровите в ръководството за архитектура за вашата платформа или да извършите бенчмарк.

Забележка: много съвременни платформи също предлагат обратен квадратен корен, който има скорост приблизително същата като sqrt, но често е по-полезен (напр. като имате invsqrt, можете да изчислите както sqrt, така и div с едно умножение за всяко).

person Suma    schedule 30.07.2011
comment
За sandy bridge от intel и двете операции отнемат точно едно и също време. Така че сега sqrt не е 2 пъти по-бавен от div - person osgx; 30.07.2011
comment
ДОБРЕ. Коригиран. Би било възможно да се включат точни времена за много платформи, но мисля, че въпросът иска да има само вътрешно усещане, в редките ситуации наистина се нуждаете от точни данни, по-важно е да знаете къде или как можете да ги получите. - person Suma; 30.07.2011
comment
Два точни примера ми дават някакво чувство. - person osgx; 30.07.2011