980, 1080, P100

GPU board close shot

This is a follow up on the “Pascal and Polaris” post I did some time ago.

As you might remember, based on the pre-released benchmarks and some rough estimations, I got to the conclusion that the 1080/1070 (Pascal) seems to have exactly the same performance per clock as Titan X (Maxwell). It seemed to be about 40% more energy efficient, which is the expected benefit from a new process (16nm) and the FinFet transistors. Being cheaper is a bonus.

Well, today we have the Tesla P100 (GP100 core) white paper, as well as the GTX 1080/1070 (GP104 core) white paper. And here are some important things …

Without further ado, here is how a streaming multiprocessor looks like in P100.

Pascal GP100 SMX design

We can see a few things – there are double precision (DP) calculation units on top of the single precision 32bit (core) ones, we have 32kb registers for each 32FP units (1 to 1 ratio), we have 64kb of shared memory for 64 FP units (1 to 1 ratio).

Lets move to 1080 (GP104).
Pascal GP104 SMX designWe can spot easily a few differences. First, there are no double precision (64bit) units. Second, we can see that there are 16kb of register for 32 single precision (1 : 2 ratio), there is 96kb of shared for 128 cores (1 : 1.3 ratio).

Now, let me get from the Maxwell white paper how the old Maxwell (Titan X/980/970/etc) streaming multiprocessors looked like.
Maxwell SMX design

So, do you see the difference between the Maxwell SMX and the GP104 SMX ? Well, no need to scroll up, because there is no difference. The GP104 is actually the good old Maxwell architecture build into 16nm transistors, instead of 28nm ones. With very few (if any) adjustments. And thats why the performance per clock of GP104 is exactly the same as Maxwell GM204. Making architecture work with two different memory types (GDDR vs HBM) is very hard from what I have heard, and I always asked myself how NVIDIA are going to achieve that. Now I know.

This of course means that all the GPGPU features of Pascal, like native half 16bit and double 64bit support, more registers, more shared memory, shared virtual memory with the CPU, HBM2.0 and NVLINK will be exclusive for the premium professional Tesla P100 (GP100). The compute preemption seems to be in the 1080 GP104 however. And the Pascal Quadro/Tesla GPUs seems to be quite different to the gamer ones, at last.

I can guess that this will be the case until the HBM2 becomes cheaper. Which will take a while.

Pascal and Polaris

pascal vs polaris

So the early reports claim that GTX 1080 is about 25% (1.25) faster compared to Titan X in game performance. This is achieved however with 2560 CUDA cores, compared to the 3072 CUDA cores of the Titan X, or with 20% (1.2) less. Overall, this gives 50% (1.2 x 1.25) better performance in game performance.

However 1080 runs at 1.6GHZ base clock, which is >50% more compared to the ~1GHZ for the Titan X.

In the best case scenario, we have 0 (zero) improvement in game performance per clock in Pascal compared to Maxwell.

But 1080 (GP104) gets exactly the same game performance per clock as Titan X (GM200) for 180W, compared to the 250W of the Titan X, which is ~40% improvement. Meaning, that most likely the Pascal architecture is exactly as efficient as Maxwell for games and that this 40% improvement are coming from the new 16nm FinFet process (the lower price comes as a bonus).

I expect there to be some (or a lot) of improvement per clock for GPGPU apps, mainly because of the doubled amount of registers – 32k vs 16k (game shaders care about those register far less compared to the GPGPU apps) and the native 16bit floating point number support. Pascal has a number of other GPGPU specific features, like: compute preemption, which allows using of GPGPU apps with 1 GPU without the OS UI becoming sluggish; shared virtual memory with the CPU; nvlink which should allow stacking of the GPU memory frame buffers (so 2 GPUs with 16GB memory each will give 32GB of usable memory); more shared memory. All of these take a lot of transistors to make and don’t really contribute to gaming. Finally after Kepler and Maxwell, it seems like NVidia are focusing not only in games …

Good thing is that it seems that AMD Polaris should be better suited compared to the current GCN architecture as well. It will have new L2 Cache (critical for GPGPU) and new memory controller. On top of that it seems to have new dynamic instruction scheduler, which happens to help a lot of the complex GPGPU apps (this was last seen in the nVidia Fermi GPUs, which probably was one of the reasons the Fermi cores were that powerful). AMD will most likely go for performance per watt, and not necessarily the highest performance (so the Polaris GPUs will be relatively small).

All we have to do is wait and see if all of this will actually work …

p.s. check the follow up of this post here.

no except

Ето една интересна лекция на Meyers, в която той обяснява много добре какво е std::move, std::forward и noexcept 🙂

Към средата има една част, в която е показано колко по-бърз може да е кода, когато е с noexcept там, където е възможно. Освен това имаше показани примери, как чрез проста смяна на компилатора със C++11/14 enabled такъв, кода става по-бърз. Във връзка с noexcept се появи в един момент и тази графика.

Колко хубаво, помислих си .. Ще направим сега едно NOEXCEPT макро (което ще е #define NOEXCEPT noexcept когато компилираме със С++11/14 компилатор), ще прекомпилираме всичко което имаме (и вкъщи и в офиса) с него и ще получим ей така едни проценти забързване от нищото ..

Та изрових една моя стара (С++98) микро библиотека за математика (вектор, точка, матрица, quaternion, в общи линии това) и реших да завъртя един цикъл с тях (тя всъщност е толкова проста, че не очаквах каквито и да е разлики в резултатите).
Ползвам clang-600.0.51(llvm 3.5svn), компилирам с -Os (което е по дефаулт) (с -O3 резултатите са почти същите) и C++14 enabled.

Кода от теста се намира тук.

Интересно, че противно на приказките на Meyers, Chandler (from LLVM) и не знам си кой друг, на моята машина (I7-3615QM) кода върви по-бавно с между 2 и 5 процента, когато функциите имат noexcept.
Или аз го ползвам много не както трябва, или някой някъде малко послъгва …

No country for old men

Още един “benchmark” между ARM и Intel CPUs (идеята за него дойде след като разни JS benchmark-ове на iPad Mini Retina минаваха само 2-3-4 пъти по-бавно от колкото на i7 2600 … Логичният въпрос, който някой (Тео) зададе беше дали наистина е толкова по-бавен, на приложение като raytracer).

Тестваме smallpt (не че това е представителен raytracer, но е raytracer), naive quick sort, random mem access & floating point operations. Кода на тестовете е тук.

Тестваме I7-3720QM (2.6GHZ) (Macbook Pro Retina 2012) vs 64-bit A7 (1.3GHZ) (iPad Mini Retina).

Тестваме еднонишково.

Тестваме с clang-503.0.40 (based on LLVM 3.4svn) (с опции -O3, relaxed IEEE & strict aliasing).

Основният тест е smallpt, покрай него има и няколко проби с по-прости сметки.
Тестовете са правени по 5 пъти (да, само 5), максималната разликата в резултатите на Intel-a е в рамките на 8% (което е бая), а на iPad-a e в рамките на 2%.

По време на тестовете не е имало други пуснати приложения (а лаптопа не е бил в power-save mode, etc). Сиреч, почти може и да си помисли човек да им вярва.

Показани са усреднените стойности на времето, което е отнел всеки тест в секунди (тоест, по-ниските правоъгълничета означават по-добри резултати).

Ако някой има С/С++/Objective-C/Swift код който иска да бъде тестван, PM me :).

Като цяло, резултатите са такива, каквито се предполагаше че ще бъдат.

There is no country for old men.

p.s. през *почти* същите тестове мина и един iPhone 4, с ~ резултати Raytrace:240s, Sort:72s, Mem:56s, FPU 21s & iPhone 6 – Raytrace 13s, Sort 11s, Rand mem access 9.5s, FPU 1.92s.

VTable по поръчка

Ето в това видео от Going Native 2013 A. Alexandrescu разказва за някои от проблемите, които виртуалните функции в С++ създават. А именно : всеки обект държи по един (или повече) 64 битов (най-често) указател към виртуалната таблица, това измества разни неща от cache-а; винаги този указател седи в началото на обекта (а в началото е добре, да има неща които се ползват по-често. Това може да е указателят към виртуалната таблица, но може да е друго :)). Рядко в живота може човек да се докара до там, че да иска и това да забързв. Но се случва.

Идеята е да си напишем наша имплементация на виртуални функции, която да ползва не 64 битово число, ами 8 (така ще се ограничим по максимален брой виртуални функции в интерфейс или по максимален брой класове в йерархия, но 256 (2^8) често е повече от достатъчно).

Ето и примерна имплементация (с малко macro-та и нагласявки :)). На места е малко redundant; мисля че може да се постигне и по-приятен синтаксис.

Не успях да събера мотивация да направя тестове за да видя има ли забързване, в кои случаи има и колко е то. Според Alexanderscu Facebook работи 4% по-бързо заради тази магия :).
Предимство е, че така можем и да изберем къде да поместим този “char”, който ползваме като указател към виртуална таблица -> ако не се очаква виртуални методи да се викат често, навярно е по-добре да не е в началото на обекта.

Raytrace Interop

Oпитвам се напоследък да правя разни бързи неща с CUDA.
Често има проблем с вземането на резултата от GPUs app (то не става особено бързо, да започне да се случва асинхронно е голяма болка, а пък понякога трябва да става особено често), та един начин това да се случва е с CUDA/OpenGL interop.

Идеята (поне моята) е с някой CUDA kernel да рисувам нещо в някоя текстура, а с OpenGL само да си я рисувам на екрана. И така, понеже всичко си е на едно място (ze gpu), да спестя всякакво четене/писане от видео паметта.

В резултат на ровене из форуми, stackoverflow, tutorials && references, ето какво се получи :

1. YouTube (beware of the video compression)(out-of-core version).
2. GitHub.

Имам около 40fps, интериорна сцена с голяма лампа (cornell box), Macbook Pro, i7 2.6GHZ, nVidia 650M, 512×512 resolution, ray depth 8, rays per pixel 1.

Това, което се случва е : CPU-то инициализира CUDA & OpenGL, стартира CUDA kernel които трасира няколко лъча, резултата се записва в текстура, и накрая се рисува с OpenGL.

Целта на кода по-горе е да се ползва като бърз startup за някои CUDA driven apps (като raytracers :)).

Pros на моят вариант : почти всичко се смята на GPU-то (генериране на семпли, random числа, трасиране, събиране на резултата), ползва Halton редици (beware, nVidia държат патент над тях), ползва CUDA/OpenGL interop (сиреч, ъпдейта на картинката се случва максимално бързо).

Cons : това не е production, aми по-скоро naive имплементация на ray trace, може да се стане няколко пъти по-бързо и noise-free (няма shadow rays, complex sampling), GPU-тата се feed-ват от 1 CPU thread, etc.

Халтон и патентите

Днес си поиграх нагледно да изпробвам това и да го сравня с псевдо-рандом генератор на ActionScript 3 🙂 (full link here)

п.с. ползването на подобни редици в CG е патентовано, така че моля разпространявайте тази страница само апокрифно …

Singleton & C++11

От време на време всеки ползва Singleton като (анти) шаблон за дизайн.
От време на време пишем и многонишкови програми.
От време на време ползваме тези двете в една програма.

Сложното тогава е, как да сме сигурни че ще имаме точно една инстанция на обекта, който желаем.
Нишките трябва да се разберат една да създаде инстанцията, а другите да не и се пречкат.

Наивният начин за имплементиране на това е с обикновен lock :

Така нещата ще потръгнат. Проблема е, че в (1) ще имаме lock-ване всеки път, когато викаме get(). Това може да е много често. А всъщност имаме нужда от 1 локване – само при създаването на обекта (тоест в (2)).

От тази книга навремето научих за double-checking метода. В общи линии, идеята е проста.

Вместо да локваме всеки път, локваме само ако няма instance (1). След това локваме и проверяваме пак (2) – така хем имаме синхронизация, хем почти няма локване.
Като начин това работи *почти* винаги, и заради това е известен anti-pattern.

Без да е volatile указателя, компилатора(или интерпретатора) могат да спретнат кофти номер. Въпреки че логически изглежда абсолютно коректен, тов не е съвсем така. Както в С++98, така и в Java, така и навярно в други езици. Заради разни полу-странни оптимизации описани тук.

Ако указателя е volatile, тогава всичко се случва както се очаква.
Той обаче може да се ползва и на други места из кода – това че е volatile може да причини overhead именно там – онези полу-странни оптимизации ще бъдат блокирани и на места, на които не е нужно.

А и нещата с тази проста идея почнаха да загрубяват.
Lock, volatile, double-checking, overheads.

Сега, по темата. Със С++11 освен unique_ptr и move constructors получаваме още няколко неща. Ето една извадка от стандарта.

Shall wait for completion of the initialization.
Така, с новият стандарт, всичко казано дотук може да бъде заменено със следното.

Програмата самичка ще се грижи нишките да се изчакат правилно.
А с решаването на всички възможни проблеми се заемат хората, пишещи компилатори :).

п.с.
В новият стандарт се намира и това, което също може да реши проблема сравнително елегантно, с неизвестено какъв overhead.
Anyway, pretty cool stuff.