Pull to refresh

Comments 16

Вы мне как раз помогли с небольшой задачкой по оптимизации, спасибо.


Но вот ещё вопрос по теме: можете рассказать что-нибудь про оптимизации работы с диском?

Вау! Спасибо за статью, узнал много нового
оптимизация доступа в память, блочная обработка — это одно из первых действий. загрузка кеш-линиями, блоки под размер кешей. при использовании нескольких потоков — cache sharing, в том числе false sharing (доступ к разным частям одной кеш-линии). я обычно сразу смотрю в ассемблерный код, чтобы понять что и как и очень подозрительно отношусь к использованию vector и прочих STL в коде, где нужна производительность — проверки диапазона индексов, выделения памяти — всё дает накладные расходы, ограничивает оптимизации, часто до С-стиля спускаюсь. Если целевая машина понятна, то можно сразу писать с расчетом на векторизацию и доступный набор инструкций, использование intrinsic со своеобразными инструкциями, которые компилятор маловероятно что применит (но шанс дать надо). Я еще компилирую вычислительное ядро gcc, clang и что есть под рукой (godbolt в помощь), чтобы быстро проверить потенциал автоматической компиляции, взять от компилятора удачные элементы и помочь где надо ручками. У Intel раньше много материалов было на тему подходов к оптимизации, сейчас с разгона мало что могу найти — software.intel.com/content/www/us/en/develop/articles/intel-parallel-computing-centers-training.html, software.intel.com/content/www/us/en/develop/videos/optimization-for-intel-parallel-architectures.html
С чего вдруг у вектора проверки индексов взялись? По стандарту никаких проверок быть не должно.
по стандарту vector и array — вроде как zero-cost abstractions, которые не должны ухудшать код, а проверки должны удаляться на этапе компиляции, выносится из цикла по возможности, но как их наличие сказывается на оптимизации — вопрос весьма интересный. В Gnu libstdc++ их вроде как нет — только косвенное обращение, в libstdcxx они как asserts github.com/llvm-mirror/libcxx/blob/master/include/vector#L1546. Но даже само наличие косвенных обращений влияет на возможности оптимизации — компилятор должен вынести их из цикла, количество проходов ограничено, итп.
безусловно компилятор может сделать очень многое и с каждой новой версией качество оптимизаций возрастает. с gcc использование __builtin_expect() и __builtin_expect_with_probability() для указания наиболее вероятного значения по моим экспериментам давало хороший результат в коде, в ряде случаев __builtin_assume_aligned() оказывался полезным (скажем void * указатель, который на самом деле всегда выровнен при использовании функции). Но форма записи цикла, проверки граничных условий тоже сильно влияет на качество кода, причем зависит от версии к версии компилятора. По конкретный компилятор можно научится писать так, чтобы код был качественный, но если нужна поддержка нескольких компиляторов приходится идти на компромиссы или использовать ассемблер или intrinsics.
Вообще-то есть likely. А использование void* в C++ выглядит оправданным только при привязке информации пользователя к объектам из библиотеки. Нормальная форма записи цикла в C++ это ranged for и не припомню чтобы она как-либо менялась.
likely задаёт ожидаемый путь исполнения, что тоже полезно. __builtin_expect() задает ожидаемое значение выражения для количества итераций, что влияет на решения по оптимизации. в принципе это можно получить в процессе профилировки, если есть такая возможность на целевой платформе. мы говорим о разных вещах тем не менее — я про то как выжать максимум из компилятора, а вы про то как в С++ это правильно написать.
#pragma GCC unroll, iv_dep, aligned, optimize, итп — расширения компилятора и в стандарте С++ отсутствуют, но бывают полезны когда надо выжать максимум.
while, do в C++ никто не отменял, да и goto в общем-то тоже. да, некрасиво, зато позволяет обойти узкие места.
Мне трудно представить ту платформу, на которой получится скомпилить C++, но не получится скомпилить с учётом профайл запуска. Такая вообще есть или как с 12 битным byte?

Для части этих прагм вообще есть синтаксис в языке. Тот-же alignas и volatile например.

Я не про то как надо на C++ писать, а про то, что все эти мифически хорошие оптимизации по факту оказываются почти бесполезными. То-же выравнивание по размеру кэш линии в нормальной программе не даст и 1% прироста, от того что в этой многопоточной части программа находится меньше процента времени.

goto это отличный способ зарубить рабочую программу. Его и ещё много чего давно стоило бы вырезать из C++, но ох уж эта обратная совместимость…
Например embedded платформы разных видов с 128-256KB RAM, 256K-1MB Flash, 25-100МГц, но надо криптографию реализовать с приемлемой скоростью — профилирование очень затруднительно, разве что в симуляции или виртуальной машине.
alignas — относительно свежее (по моим меркам %) нововведение, раньше надо было либо вручную выравнивать, либо нестандартные расширения использовать.
Абсолютно согласен, что для большинства обычных приложений нет смысла всем этим заниматься. Кроме тех случаев, когда без этого никак :) Скажем, обработка сетевых пакетов 40Gbit, при 64х байтных пакетах времени на классификацию — порядка 12 нс, да можно разбить на несколько ядер… но доступ к памяти — 100+нс, и без оптимизации под кеш не обойтись. И там либо ты успеваешь все обработать, либо будут потери пакетов. Но там на С++ мало кто пишет — обычно чистый С внизу или что-то типа С+ с очень выборочным набором используемых средств. Даже чередование банков памяти (interleaving) при выделении буферов начинает играть роль, не говоря уже про performance counters для анализа непредсказанных переходов, кеш-промахов, загрузку исполнительных блоков процессора, итп — приходится изучать не только где тормозит, но еще и почему.

не совсем. Методы std::vector::at и std::array::at будут проверять выход за границы и кидаться исключениями. Но вот их operator[] никакие проверки вводить не будет, по крайней мере в релиз режиме.

Предлагаю вам прочитать статью и поискать там at(). Даже яндекс изначально его не использует.
я лишь привел пример где std::vector/array таки добавляет проверку. Еще msvc в дебаг режиме их сует.
Даже яндекс изначально его не использует.
да стопудово где-нибудь да использует…
А я могу привести пример где std в 3-6 раза замедляет. И это ничего не докажет. Случайно поюзать at и думать «а чаво это производительность плохая» не получится.

msvc и в релизе может проверок напихать, главное правильно попросить.

да стопудово где-нибудь да использует…
И где же в статье at?

Вызов Min тоже надо убрать из цикла. Сначала обрабатываем большую часть матрицы, которая целиком помещается в кэш линии и в конце уже остаток.

Sign up to leave a comment.