#c #string #performance #initialization
#c #строка #Производительность #инициализация
Вопрос:
Я измерял скорость инициализации строк с помощью следующего кода и обнаружил нечто странное:
#include <stdio.h>
#include <time.h>
#define START_COUNTING clock_gettime(CLOCK_PROCESS_CPUTIME_ID, amp;start)
#define END_COUNTING clock_gettime(CLOCK_PROCESS_CPUTIME_ID, amp;end)
#define NUM_OF_LOOPS 8192
long time_difference(struct timespec end, struct timespec beginning) {
long long diff = (long long)end.tv_sec - (long long)beginning.tv_sec;
return(end.tv_nsec - beginning.tv_nsec ((diff > 0) ? 1000000L : 0));
}
long average(long numbers[NUM_OF_LOOPS]) {
long long sum = 0;
int i;
for(i = 0; i < NUM_OF_LOOPS; i )
sum = numbers[i];
return sum / NUM_OF_LOOPS;
}
int main(void) {
struct timespec start;
struct timespec end;
long time_diffs[NUM_OF_LOOPS];
int i;
char * str = NULL;
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = "T";
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
printf("%li ns - assigning 2 chars to a stringn", average(time_diffs));
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = "Testing";
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
printf("%li ns - assigning 8 chars to a stringn", average(time_diffs));
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = "Testing it here";
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
printf("%li ns - assigning 16 chars to a stringn", average(time_diffs));
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit nullam.";
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
printf("%li ns - assigning 64 chars to a stringn", average(time_diffs));
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = ""; // String omitted because it would waste a lot of space
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
printf("%li ns - assigning 1024 chars to a stringn", average(time_diffs));
return 0;
}
при профилировании я всегда получаю этот результат:
1126 ns - assigning 2 chars to a string
828 ns - assigning 8 chars to a string
832 ns - assigning 16 chars to a string
834 ns - assigning 64 chars to a string
857 ns - assigning 1024 chars to a string
Первый результат, независимо от того, присваивается ли он 2 символам, 8 символам или сколько угодно, всегда значительно медленнее, чем остальные. Я попытался поместить 1 назначение перед циклами, но это все равно не сильно изменило результаты.
Кто-нибудь знает, почему первые несколько раз присваивание строк происходит медленнее? Я осмотрелся, но нашел только ответ, который произошел из-за JIT-компилятора, но C их не использует.
Комментарии:
1. Возможно, кэширование процессора? Загрузка данных по требованию?
2. Количество символов не имеет значения — в любом случае он просто присваивает указатель. Первое выполнение функции может быть медленнее, поскольку требуемые данные должны быть загружены в память.
Ответ №1:
Ваш код фактически не копирует никаких символьных данных, поскольку вы просто устанавливаете указатель на строковый литерал.
Таким образом, вся предпосылка неверна, и нет никакой корреляции между количеством «назначенных» символов и затраченным временем.
Это супер-микро-бенчмарк, и его очень-очень сложно выполнить правильно, поскольку вы, по сути, пытаетесь измерить время, затрачиваемое на одно назначение размера указателя, что на самом деле не так уж много кода.
Разница в скорости может быть вызвана задержкой в кэше / памяти. Вы можете прочитать сгенерированный код, чтобы выяснить, str
хранится в регистре или нет, и, конечно, также поиграть с настройками оптимизации.
Примечание: ваш метод измерения, усреднение, означает, что объем кэша служебных данных ( time_diffs
массива) намного больше, чем у самого теста, так что, вероятно, это и является причиной нерегулярности, в первый раз time_diffs
не кэшируется.
Комментарии:
1. Я никогда не говорил, что мой код копирует какие-либо данные. Какое это имеет отношение?
2. @TheRedFox Вы сказали «неважно, назначается ли 2 символа, 8 символов или сколько угодно».
Ответ №2:
Здесь есть два важных пункта:-
- Не используйте отладочный код для синхронизации
- Убедитесь, что вы действительно что-то рассчитываете
Первое очевидно, второе нет. Например, этот код:-
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
str = "T";
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
вполне вероятно, что это будет скомпилировано оптимизирующим компилятором:-
for(i = 0; i < NUM_OF_LOOPS; i ) {
START_COUNTING;
END_COUNTING;
time_diffs[i] = time_difference(end, start);
}
str = "T";
Причина медленного выполнения первого цикла, вероятно, кэширование. Процессору приходится загружать код из RAM / HD, а это занимает некоторое время, тем более что у вас там есть вызов функции. Попробуйте поместить это перед циклами синхронизации:-
START_COUNTING;
END_COUNTING;
и, надеюсь, код занесен в кэш, готовый к циклам синхронизации.