Есть бекенд, на котором крутится много веб-приложений.
Хочется достичь двух целей:
1. Снизить время генерации страницы, чтобы гугл и юзеры полюбили наш сайт.
2. Снизить потребление ресурсов CPU, чтобы меньше бекендов нужно было.
Так вот внедряя разные оптимизации в коде мы обычно делаем разные замеры.
Чаще всего замеряем просто среднее кол-во времени, необходимое на обработку одного http-запроса.
Среднее за сутки, т.к. за бОльшее кол-во времени слишком долго ждать. Ну и вообще хотелось бы как-то сделать так, чтобы и суток не нужно было ждать - посмотрел 5 минут, пособирал данные, вычислил среднее, и делаешь вывод - улучшилась производительность или ухудшилась.
Так вот время выполнения сферического алгоритма зависит в т.ч. от нагрузки на сервер. Т.е. можно что-то изменить в коде, выкатить в продакшн, и увидеть, что нагрузка изменилась не в ту сторону, в которую на самом деле изменилась эффективность алгоритма, т.к. просто сейчас часпик или день недели такой, что много запросов отправляют пользователи.
Я попробовал замерять только юзерское время (как утилита time замеряет).
Но когда сервер загружен не на 100%, эта метрика так же чувствительна на загруженности сервера (полупустой сервер работает почти в 2 раза быстрее, чем загруженный на 100%).
Загрузкой на 100% здесь можно считать выполнение стольки однопоточных процессов, съедающих всё доступное время процессора, сколько физических процессорных ядер есть на сервере.
Т.е. там, где включен hyperthreading, нужно запустить таких процессов в 2 раза меньше, чем видно ядер, а где выключен - столько же, сколько видно ядер.
Вот так я замерял:
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw(time);
my $forks = shift;
my $lscpu = `lscpu`;
my($cpus) = $lscpu =~ /^CPU\(s\):\s+(\d)$/m;
my($threadsPerCore) = $lscpu =~ /^Thread\(s\) per core:\s+(\d)$/m;
my $cores = $cpus / $threadsPerCore;
sub load {
my $a = 0;
$a += rand() foreach(0 .. 100000000)
}
fork() for (1 .. $forks);
my $u = - times();
my $t = - time();
load();
$u += times();
$t += time();
printf "| %d | %d | %d | %.2f | %.2f |\n", $cpus, $cores, 2 ** $forks, $t, $u;
И вот мои замеры на машине с hyperthreading:
|----|-----|-----|-------|---------|
|CPUs|Cores|Procs| Time |User time|
|----|-----|-----|-------|---------|
| 4 | 2 | 1 | 11.08 | 11.07 |
| 4 | 2 | 2 | 11.70 | 11.69 |
| 4 | 2 | 4 | 19.79 | 19.64 |
| 4 | 2 | 8 | 39.42 | 19.62 |
| 4 | 2 | 16 | 83.36 | 19.86 |
|----|-----|-----|-------|---------|
И на машине без hyperthreading:
|----|-----|-----|-------|---------|
|CPUs|Cores|Procs| Time |User time|
|----|-----|-----|-------|---------|
| 2 | 2 | 1 | 23.74 | 23.73 |
| 2 | 2 | 2 | 23.53 | 23.52 |
| 2 | 2 | 4 | 46.78 | 23.38 |
| 2 | 2 | 8 | 93.76 | 23.43 |
|----|-----|-----|-------|---------|
И на этой машине User time везде примерно одинаков!
Но что же не так с первой машиной? Как только я загружаю на ней больше физических ядер, чем у неё есть, user time увеличивается.
Это что? Магия hyperthreading? Но ведь в htop видно, что во время теста загружено лишь одно виртуальное ядро из 4-х.
Запустил на ещё одной машине с hyperthereading:
|----|-----|-----|-------|---------|
|CPUs|Cores|Procs| Time |User time|
|----|-----|-----|-------|---------|
| 8 | 4 | 1 | 6.23 | 6.18 |
| 8 | 4 | 2 | 6.20 | 6.16 |
| 8 | 4 | 4 | 8.38 | 8.33 |
| 8 | 4 | 8 | 19.95 | 11.90 |
| 8 | 4 | 16 | 33.71 | 11.98 |
|----|-----|-----|-------|---------|
Тут user time растёт пока не загрузим все 8 виртуальных ядер, а не реальных.