При оптимизации кода, критичного к производительности, я заметил, что перебор по std :: set был немного медленным.
Затем я написал бенчмаркер и протестировал скорости итерации по вектору с помощью итератора (auto it : vector
), итерации по набору с помощью итератора и итерации по вектору по индексу (int i = 0; i < vector.size(); ++i
).
Контейнеры построены одинаково, с 1024 случайными числами. (Конечно, каждый int уникален, так как мы работаем с наборами). Затем для каждого запуска мы перебираем контейнер и суммируем их целые в длинное целое. Каждый прогон состоит из 1000 итераций, которые составляют сумму, и тест был усреднен за 1000 прогонов.
Вот мои результаты:
Testing vector by iterator
✓
Maximum duration: 0.012418
Minimum duration: 0.007971
Average duration: 0.008354
Testing vector by index
✓
Maximum duration: 0.002881
Minimum duration: 0.002094
Average duration: 0.002179
Testing set by iterator
✓
Maximum duration: 0.021862
Minimum duration: 0.014278
Average duration: 0.014971
Как мы видим, повторение набора с помощью итератора в 1,79 раза медленнее, чем по вектору, и колоссальное в 6,87 раза медленнее, чем по индексу.
Что здесь происходит? Разве набор не является просто структурированным вектором, который проверяет, является ли каждый элемент уникальным при вставке? Почему это должно быть намного медленнее?
Редактировать: Спасибо за ваши ответы! Хорошие объяснения. По запросу вот код эталонного теста.
#include <chrono>
#include <random>
#include <string>
#include <functional>
#include <set>
#include <vector>
void benchmark(const char* name, int runs, int iterations, std::function<void(int)> func) {
printf("Testing %s\n", name);
std::chrono::duration<double> min = std::chrono::duration<double>::max();
std::chrono::duration<double> max = std::chrono::duration<double>::min();
std::chrono::duration<double> run = std::chrono::duration<double>::zero();
std::chrono::duration<double> avg = std::chrono::duration<double>::zero();
std::chrono::high_resolution_clock::time_point t1;
std::chrono::high_resolution_clock::time_point t2;
// [removed] progress bar code
for (int i = 0; i < runs; ++i) {
t1 = std::chrono::high_resolution_clock::now();
func(iterations);
t2 = std::chrono::high_resolution_clock::now();
run = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
// [removed] progress bar code
if (run < min) min = run;
if (run > max) max = run;
avg += run / 1000.0;
}
// [removed] progress bar code
printf("Maximum duration: %f\n", max.count());
printf("Minimum duration: %f\n", min.count());
printf("Average duration: %f\n", avg.count());
printf("\n");
}
int main(int argc, char const *argv[]) {
const unsigned int arrSize = 1024;
std::vector<int> vector; vector.reserve(arrSize);
std::set<int> set;
for (int i = 0; i < arrSize; ++i) {
while (1) {
int entry = rand() - (RAND_MAX / 2);
auto ret = set.insert(entry);
if (ret.second) {
vector.push_back(entry);
break;
}
}
}
printf("Created vector of size %lu, set of size %lu\n", vector.size(), set.size());
benchmark("vector by iterator", 1000, 1000, [vector](int runs) -> void {
for (int i = 0; i < runs; ++i) {
long int sum = 0;
for (auto it : vector) {
sum += it;
}
}
});
benchmark("vector by index", 1000, 1000, [vector, arrSize](int runs) -> void {
for (int i = 0; i < runs; ++i) {
long int sum = 0;
for (int j = 0; j < arrSize; ++j) {
sum += vector[j];
}
}
});
benchmark("set by iterator", 1000, 1000, [set](int runs) -> void {
for (int i = 0; i < runs; ++i) {
long int sum = 0;
for (auto it : set) {
sum += it;
}
}
});
return 0;
}
Я работаю над публикацией результатов, используя O2, но я пытаюсь получить компилятор, чтобы избежать оптимизации суммы.