Подтвердить что ты не робот

Делать кеши atoi() и atof()? Кажется, что они быстрее исполняются,

Я использовал _rdtsc() для времени atoi() и atof(), и я заметил, что они занимают довольно много времени. Поэтому я написал свои собственные версии этих функций, которые были намного быстрее первого вызова.

Я использую Windows 7, VS2012 IDE, но с компилятором Intel C/С++ v13. У меня есть -/O3 включен, а также -/Ot ( "favor fast code" ). Мой процессор - это мост Ivy (мобильный).

При дальнейших исследованиях казалось, что чем больше раз были названы atoi() и atof(), тем быстрее они выполнялись?? Я говорю быстрее:

Когда я вызываю atoi() из моего цикла, только один раз, он занимает 5 892 цикла процессора, но после тысяч итераций это уменьшилось до 300-600 циклов процессора (довольно большой диапазон времени выполнения).

atof() изначально занимает от 20 000 до 30 000 циклов процессора, а затем спустя несколько тысяч итераций он занимал 18-28 циклов процессора (это скорость, с которой моя пользовательская функция выполняется в первый раз, когда она вызывается).

Может ли кто-нибудь объяснить этот эффект?

EDIT: забыли сказать - базовой настройкой моей программы был цикл, анализирующий байты из файла. Внутри цикла я, очевидно, использую мой atof и atoi, чтобы заметить выше. Однако то, что я также заметил, заключается в том, что, когда я делал свое исследование перед циклом, дважды вызывал atoi и atof дважды, вместе с моими эквивалентными пользователем функциями дважды, казалось, что цикл выполнялся быстрее. Цикл обработал 150 000 строк данных, каждая строка требует 3x atof() или atoi() s. Еще раз, я не могу понять, почему вызов этих функций до того, как мой основной цикл повлиял на скорость программы, вызывающей эти функции 500 000 раз?!

#include <ia32intrin.h>

int main(){

    //call myatoi() and time it
    //call atoi() and time it
    //call myatoi() and time it
    //call atoi() and time it

    char* bytes2 = "45632";
    _int64 start2 = _rdtsc();
    unsigned int a2 = atoi(bytes2);
    _int64 finish2 = _rdtsc();
    cout << (finish2 - start2) << " CPU cycles for atoi()" << endl;

    //call myatof() and time it
    //call atof() and time it
    //call myatof() and time it
    //call atof() and time it


    //Iterate through 150,000 lines, each line about 25 characters.
    //The below executes slower if the above debugging is NOT done.
    while(i < file_size){
        //Loop through my data, call atoi() or atof() 1 or 2 times per line
        switch(bytes[i]){
            case ' ':
                //I have an array of shorts which records the distance from the beginning
                //of the line to each of the tokens in the line. In the below switch
                //statement offset_to_price and offset_to_qty refer to this array.

            case '\n':

                switch(message_type){  
                    case 'A':
                        char* temp = bytes + offset_to_price;
                        _int64 start = _rdtsc();
                        price = atof(temp);
                        _int64 finish = _rdtsc();
                        cout << (finish - start) << " CPU cycles" << endl;
                        //Other processing with the tokens
                        break;

                    case 'R':
                        //Get the 4th line token using atoi() as above
                        char* temp = bytes + offset_to_qty;
                        _int64 start = _rdtsc();
                        price = atoi(temp);
                        _int64 finish = _rdtsc();
                        cout << (finish - start) << " CPU cycles" << endl;
                        //Other processing with the tokens
                        break;
                }
            break;
        }
    }
}

Строки в файле похожи на это (без пробелов между ними):

34605792 R dacb 100

34605794 Рабс S 44.17 100

34605797 R kacb 100

34605799 Сабль S 44.18 100

34605800 R nacb 100

34605800 A tacb B 44,16 100

34605801 R gacb 100

Я использую atoi() для 4-го элемента в сообщениях "R" и 5-го элемента в сообщениях "A" и используя atof() для 4-го элемента в сообщениях "A".

4b9b3361

Ответ 1

Я предполагаю, почему вы видите такое резкое улучшение для atoi и atof, но не для вашей собственной, более простой функции, заключается в том, что у первых есть большое количество ветвей, чтобы обрабатывать все краевые случаи. Первые несколько раз это приводит к большому числу неверных прогнозов ветвления, которые являются дорогостоящими. Но через несколько раз прогнозы становятся более точными. Правильно спрогнозированная ветвь почти свободна, что сделает их конкурентоспособными с вашей более простой версией, которая не включает ветки для начала.

Кэширование, безусловно, также важно, но я не думаю, что это объясняет, почему ваша собственная функция была быстрой с самого начала, и после повторного выполнения (если я вас правильно понял) не вижу никаких соответствующих улучшений.

Ответ 2

Использование RDTSC для профилирования опасно. В руководстве по процессору Intel:

Инструкция RDTSC не является инструкцией по сериализации. Это не обязательно ждет, пока все предыдущие инструкции были выполнены перед чтением счетчика. Аналогичным образом, последующие инструкции могут начать выполнение до выполняется операция чтения. Если программное обеспечение требует, чтобы RDTSC выполнялся только после того, как все предыдущие инструкции локально, он может использовать RDTSCP (если процессор поддерживает эту инструкцию) или выполнить последовательность LFENCE;. RDTSC

С неизбежным эффектом Гейзенберга, который вызывают, вы теперь будете измерять стоимость RDTSCP или LFENCE. Вместо этого рассмотрите измерение цикла.

Ответ 3

Измерение производительности для одного вызова, подобного этому, нецелесообразно. Вы получите слишком много дисперсий из-за силовых дросселей, прерываний и других помех ОС/системы, издержек измерения и, как сказано выше, - холодного/теплого отклонения. Кроме того, rdtsc больше не считается надежным измерением, так как ваш процессор может дросселировать свою собственную частоту, но для этой простой проверки мы можем сказать, что это достаточно хорошо.

Вы должны запустить свой код по крайней мере несколько тысяч раз, отбросить часть в начале, а затем разделить, чтобы получить среднее значение - это даст вам "теплую" производительность, которая будет включать (как указано в комментариях выше ) закрывает тайм-аут кэша для кода и данных (а также TLB), хорошего предсказания ветвления и может также отрицать некоторые внешние воздействия (например, только недавно пробудивший ваш процессор из состояния выключения).

Конечно, вы можете утверждать, что эта производительность слишком оптимистична, потому что в реальных сценариях вы не всегда попадаете в кеш L1 и т.д. - все равно может быть хорошо для сравнения двух разных методов (таких как конкуренция с библиотекой ato * функции), просто не рассчитывайте на результаты для реальной жизни. Вы также можете сделать тест немного сложнее, и вызовите функцию с более сложной структурой входов, которая будет немного улучшать кеширование.

Что касается вашего вопроса относительно циклов 20k-30k - именно по этой причине вы должны отказаться от первых нескольких итераций. Это не просто латентность кэширования, вы на самом деле ожидаете, что первые инструкции будут выполнять извлечение кода, что также может ждать перехода кодовой страницы на ход страницы (длинный процесс, который может включать в себя несколько обращений к памяти), и если вам действительно не повезло - также замена на странице с диска, что требует поддержки ОС и большого количества задержки ввода-вывода. И это еще до того, как вы начали выполнять первую инструкцию.

Ответ 4

Наиболее вероятным объяснением является то, что, поскольку вы часто вызываете atoi/atof, он идентифицируется как "горячая точка" и, таким образом, сохраняется в кеше кода процессора уровня 1 или уровня 2. Политика замены ЦП - тот микрокод, который определяет, какие строки кэша могут быть очищены при промахе кеша), помечает такую ​​горячую точку, которая будет храниться в кеше. Там, где вы заинтересованы, достойная запись технологий кэширования cpu на wikipedia.

Ваши начальные тайминги были низкими, потому что ваш код еще не был в самом мощном кеш-памяти CPU, но после вызова несколько раз были.