Самый быстрый шаффл строки

Revision ru5, by Gornak40, 2025-08-14 00:20:38

Представьте, что вам нужно перебрать много случайных перестановок строки и выполнить какую-то быструю проверку для каждой. Проверка настолько быстрая, что узким местом в вашем решении становится шаффл. Для простоты, в сниппетах я ускорил проверку до no-op и добавил печать финальной строки.

Реализация с random_shuffle

#include <bits/stdc++.h>
using namespace std;

#define ITER 10000

int main() {
    string s; cin >> s;
    for (int i = 0; i < ITER; ++i) {
        random_shuffle(s.begin(), s.end());
        // do_something(s);
    }
    cout << s << '\n';
}

Бенчмаркать будем на случайной строке [a-zA-Z0-9] длины $$$2 \cdot 10^5$$$.

Как получить такую строку на Unix?

Скомпилируем код с g++ 14.2.1 со стандартным для большинства тестирующих систем флагом -O2. На моём ноутбуке процессорное время работы составило $$$26.78$$$ секунд.

Реализация с shuffle

Возможно вы читали статью или обратили внимание на warning компилятора. Действительно, уже давно никто не использует random_shuffle, ведь есть модная замена shuffle.

#include <bits/stdc++.h>
using namespace std;

#define ITER 10000

int main() {
    string s; cin >> s;
    mt19937 rng(42); // reproducibility
    for (int i = 0; i < ITER; ++i) {
        shuffle(s.begin(), s.end(), rng);
        // do_something(s);
    }
    cout << s << '\n';
}

Процессорное время работы такой реализации в тех же условиях составило $$$7.73$$$ секунд. Круто! В рамках этой заметки мы не будем погружаться в детали реализации и анализировать причину такого ускорения.

Реализация с strfry

Обратимся к наследию C. В стандартной библиотеке иногда встречаются полезные функции, например strtok, не имеющие аналогов в современном C++. Если же заглянуть за _GNU_SOURCE, можно обнаружить куда более удивительные вещи.

Внезапно на сцену выходит glibc. С нашей задачей нам поможет strfry. Функция принимает 0-терминированную строку и шаффлит её. При этом глобальное состояние ГПСЧ не меняется, а от запуска к запуску перестановка будет меняться. Под капотом функция использует getrandom в стандартном линейном алгоритме со свопами.

#include <bits/stdc++.h>
using namespace std;

#define ITER 10000

int main() {
    string s; cin >> s;
    for (int i = 0; i < ITER; ++i) {
        strfry(s.data());
        // do_something(s);
    }
    cout << s << '\n';
}

Процессорное время работы в тех же условиях составило $$$5.54$$$ секунд. Ура, мы нашли победителя!

Tags рандом, gnu, c++, язык с, бенчмарки

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
ru9 Russian Gornak40 2025-08-14 01:03:52 0 (опубликовано)
en11 English Gornak40 2025-08-14 01:03:37 0 (published)
en10 English Gornak40 2025-08-14 01:01:19 79
en9 English Gornak40 2025-08-14 01:00:33 79 Tiny change: 'he winner!' -> 'he winner!\n\n![Chart](https://mirror.codeforces.com/aac5f4/chart.png)'
en8 English Gornak40 2025-08-14 00:54:39 1 Tiny change: 'Imagine yo' -> ' Imagine yo'
en7 English Gornak40 2025-08-14 00:52:52 3 Tiny change: ' user CPU runtime for t' -> ' user CPU time for t'
en6 English Gornak40 2025-08-14 00:51:52 15 Tiny change: ' seconds. Hooray, we've fo' -> ' seconds. Congratulations, we've fo'
en5 English Gornak40 2025-08-14 00:50:10 1 Tiny change: 'tware/libc enters th' -> 'tware/libc) enters th'
en4 English Gornak40 2025-08-14 00:49:41 15 Tiny change: 'd library sometimes contains useful fu' -> 'd library contains some useful fu'
en3 English Gornak40 2025-08-14 00:47:54 2 Tiny change: ' time was 26.78 seconds.\' -> ' time was $26.78$ seconds.\'
en2 English Gornak40 2025-08-14 00:47:03 1062
ru8 Russian Gornak40 2025-08-14 00:44:52 6
en1 English Gornak40 2025-08-14 00:43:42 3176 Initial revision for English translation (saved to drafts)
ru7 Russian Gornak40 2025-08-14 00:31:11 127
ru6 Russian Gornak40 2025-08-14 00:23:36 143
ru5 Russian Gornak40 2025-08-14 00:20:38 11 Мелкая правка: 'и добавил вывод финальной' -> 'и добавил печать финальной'
ru4 Russian Gornak40 2025-08-14 00:20:11 160
ru3 Russian Gornak40 2025-08-14 00:14:13 2065 Мелкая правка: 'ень просто: `tr -dc '[' -> 'ень просто.\n`tr -dc '['
ru2 Russian Gornak40 2025-08-13 23:22:31 26 Мелкая правка: 'troka.txt`\n' -> 'troka.txt`.\n'
ru1 Russian Gornak40 2025-08-13 23:21:22 794 Первая редакция (сохранено в черновиках)