Compare commits

...
Sign in to create a new pull request.

2 commits
master ... text

6 changed files with 388 additions and 780 deletions

162
README.md
View file

@ -1,133 +1,45 @@
# Implementing Change Data Capture
## Объяснение алгоритма FastCDC
## Объяснение изменений и особенностей
FastCDC представляет собой алгоритм для разбиения данных на "куски" (чанки) на основе их содержания, что позволяет экономить пространство в хранилищах. Этот метод быстрее и эффективнее традиционных подходов, таких как Rabin или Gear.
1. **Статическая таблица Gear**:
- Таблица `gear` определена как `immutable ulong[256]` с 256 случайными 64-битными числами, сгенерированными заранее (вместо динамической генерации с `Random`). Значения выбраны произвольно, но в стиле реализаций FastCDC, где таблица фиксирована для консистентности.
- Использование `immutable` обеспечивает неизменяемость и оптимизацию компилятором D.
- В реальных системах таблица может быть взята из оригинальной статьи FastCDC или сгенерирована с криптографически безопасным ГСЧ, но здесь она статична для простоты и воспроизводимости.
### Шаг 1: Что Такое Content-Defined Chunking (CDC) и Зачем Это Нужно?
Рассмотрим большой файл, например, видео или архив. При изменении нескольких байт весь файл может восприниматься как новый, что приводит к записи дубликатов и потере места. CDC решает эту проблему: данные разбиваются на переменные куски (чанки) не по фиксированной длине (например, 8 КБ), а по содержанию. При небольших изменениях большинство чанков остаются неизменными и не требуют перезаписи.
2. **Текст**:
- Переносы строк (`\n`) заменены на пробелы, как указано в запросе. Длина текста в UTF-8 составляет 7471 байт (кириллица — 2 байта на символ, пробелы и знаки — 1 байт).
- Текст конвертируется в `ubyte[]` для обработки FastCDC, который работает на уровне байтов.
- **Проблема с фиксированным разбиением (Fixed-Size Chunking)**: Вставка байта в начало сдвигает все чанки, и система считает весь файл новым.
- **Как работает базовый CDC**: Производится "скольжение" окна по байтам файла, вычисляется хэш (отпечаток), и определяются "границы" чанков, где хэш соответствует условию (например, заканчивается нулями).
3. **Параметры FastCDC**:
- `minSize = 100`, `normalSize = 200`, `maxSize = 500` — те же, что в предыдущем коде, подходят для текста длиной 7471 байт, обеспечивая около 3040 чанков (7471 / 200 ≈ 37).
- Маски: `maskS = 0b11111111` (8 бит, вероятность cut-point 1/256), `maskL = 0b1111` (4 бита, вероятность 1/16), уровень нормализации 2 (±2 бита от базовой log2(200) ≈ 8).
FastCDC является оптимизированной версией Gear-based CDC. Gear применяет быстрый хэш, но работает медленнее из-за частых проверок и неравномерных чанков. FastCDC ускоряет процесс в 35 раз, сохраняя высокую эффективность.
4. **Функция `fastcdc`**:
- Сигнатура упрощена: удалён параметр `gear`, так как таблица теперь глобальная и статическая (`immutable gear`).
- Логика осталась без изменений: инициализация хэша `fp` для первых `minSize` байт без проверок, фаза `maskS` до `normalSize`, затем `maskL` до `maxSize` или cut-point (`fp & mask == 0`).
### Шаг 2: Три Ключевые "Фишки" FastCDC
FastCDC использует три основных улучшения для достижения скорости и эффективности:
5. **Вывод**:
- Код выводит общую длину текста, номер чанка, его размер и первые 50 символов содержимого (или всё, если короче).
- Пример вывода (зависит от Gear-таблицы):
```
Total length (bytes): 7471
Chunk sizes and contents:
Chunk 1: 239 bytes
Content: Цель науки о данных — улучшить процесс принятия решений, о...
Chunk 2: 204 bytes
Content: сновывая их на более глубоком понимании ситуации с помощью ...
Chunk 3: 211 bytes
Content: анализа больших наборов данных. Как область деятельности на...
...
Chunk 31: 65 bytes
Content: определить, будет ли данный конкретный проект успешным.
```
1. **Оптимизация Проверки Хэша (Simplified and Enhanced Hash Judgment)**:
- В традиционных методах проверка хэша сложна (например, через деление modulo). FastCDC упрощает процесс с помощью побитового AND (&) с маской (фильтром из бит).
- Аналогия: Вместо проверки делимости числа на 100 используется проверка на окончание двумя нулями — это быстрее для вычислений.
- Дополнительно: Расширяется "окно" хэша до 48 бит с нулями в маске, что обеспечивает равномерность чанков и снижает коллизии позиций.
2. **Пропуск Границ для Маленьких Чанков (Sub-Minimum Chunk Cut-Point Skipping)**:
- Если потенциальный чанк меньше минимального порога (например, 2 КБ), возможная граница игнорируется, и вычисление хэша продолжается.
- Аналогия: При чтении книги конец абзаца пропускается, если абзац короче двух страниц, чтобы избежать траты времени на мелкие фрагменты.
- Это ускоряет алгоритм, хотя может слегка снизить эффективность дедупликации (на 15%), делая некоторые чанки крупнее.
3. **Нормализация Размеров Чанков (Normalized Chunking)**:
- Для компенсации пропусков применяются разные маски: "строгая" (MaskS, с большим количеством '1' бит — реже границы) для маленьких чанков и "слабая" (MaskL, с меньшим количеством '1' бит — чаще границы) для больших.
- Аналогия: Для маленького чанка фильтр ужесточается, чтобы позволить рост; для большого — ослабляется, чтобы timely разрезать. В результате чанки группируются вокруг идеального размера (например, 8 КБ), с распределением близким к нормальному.
- Уровень нормализации: 13 (количество бит для изменения в масках).
### Шаг 3: Параметры — Что Настраивать?
- **Ожидаемый размер чанка (Expected/Average Size, avg)**: Обычно 8 КБ (8192 байт). Это целевой размер для нормализации.
- **Минимальный размер (MinSize)**: 2 КБ, 4 КБ или 8 КБ. Ниже этого порога проверки пропускаются.
- **Максимальный размер (MaxSize)**: 64 КБ. При достижении происходит принудительное разбиение.
- **Маски (Masks)**: Битовые фильтры. Пример для avg=8 КБ:
- MaskS (строгая): 15 бит '1' (реже срабатывает).
- MaskA (базовая): 13 бит '1'.
- MaskL (слабая): 11 бит '1'.
- **Gear-таблица**: Массив из 256 случайных 64-битных чисел для хэша.
### Шаг 4: Как Работает Алгоритм — Пошагово
1. **Подготовка**: Взять буфер данных (файл как массив байт, длиной n). Установить параметры. Инициализировать хэш (fp = 0) и позицию (i = MinSize — для пропуска мелких фрагментов).
2. **Проверка краёв**: Если n ≤ MinSize — весь буфер считается одним чанком. Если n > MaxSize — обрезать до MaxSize. Если n < avg установить avg = n.
3. **Цикл для маленьких чанков (до avg, строгая маска)**:
- Для каждой позиции i от MinSize до avg-1:
- Обновить хэш: fp = (fp << 1) + Gear[src[i]] (сдвиг влево плюс значение из таблицы).
- Проверить: если (fp & MaskS) == 0 — найдена граница. Вернуть i как размер чанка.
- Это способствует росту чанков, снижая частоту разбиения.
4. **Цикл для больших чанков (от avg до MaxSize, слабая маска)**:
- Для i от avg до MaxSize-1:
- Обновить хэш аналогично.
- Проверить: если (fp & MaskL) == 0 — найдена граница. Вернуть i.
- Это увеличивает частоту разбиения, предотвращая перерост.
5. **Если граница не найдена**: Вернуть MaxSize (принудительная граница).
6. **Повтор**: Применить цикл к оставшимся данным до конца файла.
```mermaid
flowchart TD
A("Начало: Входные данные, min/avg/max размеры, маски S/L") --> B{"Достигнут конец данных?"}
B -- Да --> Z("Конец: Последний чанк")
B -- Нет --> C("Инициализировать hash fp=0, позиция i=min")
C --> D{"Чанк < min?"}
D -- Да --> E("Пропустить проверку: i++, обновить hash fp = (fp << 1) + Gear[byte]")
E --> D
D -- Нет --> F{"Чанк < avg?"}
F -- Да --> G("Проверить cut-point: fp & MaskS == 0?")
G -- Да --> H("Разбить чанк на i, начать новый")
G -- Нет --> I("i++, обновить hash")
I --> F
F -- Нет --> J("Проверить cut-point: fp & MaskL == 0?")
J -- Да --> H
J -- Нет --> K("i++, обновить hash")
K --> L{"Чанк >= max?"}
L -- Да --> H
L -- Нет --> F
H --> B
```
### Шаг 5: Псевдокод — В Стиле Языка D
Ниже приведен псевдокод в стиле языка программирования D. Предполагается, что Gear — это готовый массив ulong[256].
```d
ulong fastcdc(ubyte[] src, size_t n, size_t minSize, size_t maxSize, size_t normalSize, ulong maskS, ulong maskL, ulong[256] gear) {
if (n <= minSize) return n;
if (n > maxSize) n = maxSize;
if (n < normalSize) normalSize = n;
ulong fp = 0;
size_t i = minSize; // Пропуск мелких
// Цикл до normalSize (строгая маска)
while (i < normalSize) {
fp = (fp << 1) + gear[src[i]];
if ((fp & maskS) == 0) {
return i;
}
i++;
}
// Цикл после (слабая маска)
while (i < n) {
fp = (fp << 1) + gear[src[i]];
if ((fp & maskL) == 0) {
return i;
}
i++;
}
return n; // Достигнут max
}
```
Этот псевдокод можно использовать в цикле для обработки всего файла, собирая чанки.
### Шаг 6: Пример в Действии
Предположим файл размером 20 КБ, avg=8 КБ, min=2 КБ, max=64 КБ.
- Старт с i=2 КБ.
- Вычисление хэша до 8 КБ с MaskS — без срабатывания, продолжение.
- С 8 КБ применение MaskL — срабатывание на 10 КБ. Первый чанк: 10 КБ.
- Повтор для оставшихся 10 КБ.
Результат: Чанки примерно 816 КБ, с быстрыми вычислениями.
### Шаг 7: Преимущества и Когда Использовать
- **Плюсы**: В 3 раза быстрее Gear, в 10 раз быстрее Rabin; коэффициент дедупликации близок к оптимальному (падение менее 15%).
- **Минусы**: Эффективность зависит от данных; пропуски могут снижать производительность для очень похожих файлов.
- **Применение**: В системах типа IPFS, резервных копиях (Duplicati), хранилищах (S3 с дедупликацией).
6. **Особенности работы**:
- **Rolling hash**: Для каждого байта `fp = (fp << 1) + gear[byte]`. Статическая таблица `gear` обеспечивает одинаковые результаты при каждом запуске.
- **Нормализация**: `maskS` делает cut-points реже до 200 байт, `maskL` — чаще после, поддерживая чанки около 200 байт.
- **UTF-8**: Границы чанков байтовые, могут не совпадать с границами слов из-за кириллицы (2 байта на символ).
- **Дедупликация**: Для проверки дубликатов можно добавить хэширование чанков (например, SHA-256), но это не включено в текущий код.

View file

@ -7,5 +7,6 @@
"license": "BSL-1.0",
"name": "cdc",
"targetPath": "bin",
"targetType": "executable"
"targetType": "executable",
"dflags": ["-J=source"]
}

View file

@ -1,409 +1,262 @@
module app;
import std.stdio;
import std.string;
import std.stdio : writeln, writefln, File;
import std.file : exists, mkdirRecurse, read, write, readText, rename;
import std.path : baseName, dirName, buildPath, absolutePath;
import std.getopt : getopt;
import std.string : strip, split, splitLines;
import std.algorithm.searching : startsWith, endsWith;
import std.conv : to;
import std.datetime : Clock;
import std.exception : enforce;
import std.digest.sha : sha256Of, SHA256;
import std.format : format;
// Статическая таблица Gear (256 случайных 64-битных чисел)
mixin(import("gear.d"));
import fastcdc; // твой модуль FastCDC
void printDetails(const ubyte[] src, const ulong fingerprint, const size_t i, const size_t mask) {
// Отпечаток со сдвигом
ulong fpOffset = fingerprint << 1;
// Номер байта
short charByte = src[i];
// Значение gear
ulong gearValue = gear[charByte];
// Итоговый fingerprint
ulong fpResult = fpOffset + gearValue;
// ---------- утилиты ----------
// У Phobos read(...) на некоторых версиях возвращает void[].
// Безопасно приводим к ubyte[] в @trusted-обёртке.
@trusted ubyte[] readBytes(string path)
{
auto v = read(path); // void[]
return cast(ubyte[]) v; // это новый буфер байт → безопасно
writefln(
"\tОтпечаток = %d\n" ~
"\tОтпечаток со сдвигом (умножение на 2) = %d\n" ~
"\tПолучение байта (0-255) согласно индексу %d из исходной строки = %d\n" ~
"\tПолучение значения из таблицы gear согласно номеру байта = %d\n" ~
"\tСумма отпечатка со сдвигом с полученным значением из таблицы gear = %d\n" ~
"\tБитовое представление текущей маски = %b\n" ~
"\tБитовое представление итогового отпечатка = %b\n" ~
"\tРезультат наложения маски и отпечатка = %b\n",
fingerprint,
fpOffset,
i, charByte,
gearValue,
fpResult,
mask,
fpResult,
fpResult & mask
);
}
// hex из байтов (scope для локальных срезов)
@safe pure
string toHex(scope const(ubyte)[] bytes)
// Функция FastCDC с отладочной информацией
ulong fastcdc(const ubyte[] src, size_t n, size_t minSize, size_t maxSize,
size_t normalSize, ulong maskS, ulong maskL, size_t chunkNumber, bool debugEnabled)
{
immutable char[16] HEX = "0123456789abcdef";
auto buf = new char[bytes.length * 2];
size_t j = 0;
foreach (b; bytes)
if (n <= minSize)
{
buf[j++] = HEX[(b >> 4) & 0xF];
buf[j++] = HEX[b & 0xF];
}
return buf.idup;
}
// Путь чанка с fanout: store/chunks/aa/bb/<hash>.bin
@safe
string chunkPath(string storeDir, string hashHex)
{
auto a = hashHex[0 .. 2];
auto b = hashHex[2 .. 4];
return buildPath(storeDir, "chunks", a, b, hashHex ~ ".bin");
}
// Путь манифеста: store/manifests/<name>.<epoch>.manifest
@safe
string manifestPath(string storeDir, string srcPath, long epoch)
{
auto name = baseName(srcPath);
return buildPath(storeDir, "manifests", name ~ "." ~ to!string(epoch) ~ ".manifest");
}
// Обновить/создать "указатель" на последний манифест:
// store/manifests/<name>.latest (содержит basename файла манифеста)
@safe
void writeLatestPointer(string storeDir, string srcPath, string manifestFullPath)
{
auto manifestsDir = buildPath(storeDir, "manifests");
mkdirRecurse(manifestsDir);
auto latestFile = buildPath(manifestsDir, baseName(srcPath) ~ ".latest");
auto justName = baseName(manifestFullPath);
write(latestFile, justName); // plain text
}
// Разрешить путь манифеста: если мне дали *.latest — прочитать ссылку.
@safe
string resolveManifestPath(string storeDir, string manifestGiven)
{
if (manifestGiven.endsWith(".latest") && exists(manifestGiven))
{
auto dir = dirName(manifestGiven);
auto name = readText(manifestGiven).strip;
// если в файле относительный basename — склеиваем с текущей директорией
return buildPath(dir, name);
}
// Возможно, дали просто store/manifests/<name>.latest (существующий файл)
if (manifestGiven.endsWith(".latest") && exists(buildPath(storeDir, "manifests", baseName(
manifestGiven))))
{
auto latest = buildPath(storeDir, "manifests", baseName(manifestGiven));
auto name = readText(latest).strip;
return buildPath(dirName(latest), name);
}
return manifestGiven;
}
// Атомарная запись чанка: через временный файл и rename()
@trusted
void writeAtomic(string path, in ubyte[] data)
{
auto tmp = path ~ ".tmp";
auto f = File(tmp, "wb");
f.rawWrite(data);
f.flush();
f.close();
// ensure конечные директории существуют (на случай гонки)
mkdirRecurse(dirName(path));
rename(tmp, path); // POSIX: атомарно
}
// Создать служебные директории
@safe
void ensureDirs(string storeDir)
{
mkdirRecurse(buildPath(storeDir, "chunks"));
mkdirRecurse(buildPath(storeDir, "manifests"));
}
// Вспомогалка: записать строку в файл И одновременно накормить хэшер
@trusted
void mfWriteLine(ref File mf, ref SHA256 h, string line)
{
mf.writeln(line);
h.put(cast(const(ubyte)[]) line);
h.put(cast(const(ubyte)[]) "\n");
}
// ---------- split ----------
struct SplitOpts
{
string storeDir;
string filePath;
size_t minSize = 8 * 1024;
size_t avgSize = 64 * 1024;
size_t maxSize = 256 * 1024;
size_t logEvery = 256; // каждые N чанков логировать (0 = выкл)
string profile; // "text" | "bin" | ""
}
@safe
void applyProfile(ref SplitOpts opt)
{
if (opt.profile == "text")
{
if (opt.minSize == 8 * 1024)
opt.minSize = 4 * 1024;
if (opt.avgSize == 64 * 1024)
opt.avgSize = 32 * 1024;
if (opt.maxSize == 256 * 1024)
opt.maxSize = 128 * 1024;
}
else if (opt.profile == "bin")
{
if (opt.minSize == 8 * 1024)
opt.minSize = 16 * 1024;
if (opt.avgSize == 64 * 1024)
opt.avgSize = 128 * 1024;
if (opt.maxSize == 256 * 1024)
opt.maxSize = 512 * 1024;
}
}
@safe
int cmdSplit(SplitOpts opt)
{
enforce(exists(opt.filePath), "Файл не найден: " ~ opt.filePath);
ensureDirs(opt.storeDir);
applyProfile(opt);
// бинарное чтение исходника
ubyte[] data = readBytes(opt.filePath);
FastCDCParams p = {opt.minSize, opt.avgSize, opt.maxSize};
p.normalize();
size_t chunkCount = 0;
size_t totalBytes = data.length;
// имя манифеста
auto epoch = Clock.currTime().toUnixTime();
auto mfPath = manifestPath(opt.storeDir, opt.filePath, epoch);
auto mfDir = buildPath(opt.storeDir, "manifests");
mkdirRecurse(mfDir);
auto mf = File(mfPath, "w");
// будем сразу считать SHA-256 манифеста (кроме финальной строки manifest_sha256)
SHA256 mh;
// шапка манифеста
mfWriteLine(mf, mh, "# FastCDC manifest");
mfWriteLine(mf, mh, "path\t" ~ absolutePath(opt.filePath));
mfWriteLine(mf, mh, format("size\t%s", to!string(totalBytes)));
mfWriteLine(mf, mh, "algo\tsha256");
mfWriteLine(mf, mh, format("min\t%u", cast(uint) p.minSize));
mfWriteLine(mf, mh, format("avg\t%u", cast(uint) p.avgSize));
mfWriteLine(mf, mh, format("max\t%u", cast(uint) p.maxSize));
mfWriteLine(mf, mh, "ord\thash\tsize");
// потоковая нарезка: sha256 чанка, атомарная запись, строка в манифест
size_t ord = 0;
processStream(data, p, (size_t start, size_t len) @safe {
auto slice = data[start .. start + len];
auto digest = sha256Of(slice); // ubyte[32]
auto hex = toHex(digest[]);
auto cpath = chunkPath(opt.storeDir, hex);
// подготовим подпапки aa/bb
mkdirRecurse(buildPath(opt.storeDir, "chunks", hex[0 .. 2]));
mkdirRecurse(buildPath(opt.storeDir, "chunks", hex[0 .. 2], hex[2 .. 4]));
if (!exists(cpath))
writeAtomic(cpath, slice); // атомарная запись
// строка манифеста для чанка
auto line = format("%u\t%s\t%u", cast(uint) ord, hex, cast(uint) len);
mfWriteLine(mf, mh, line);
++ord;
++chunkCount;
if (opt.logEvery != 0 && (ord % opt.logEvery) == 0)
writefln("… %u chunks", cast(uint) ord);
return 0; // продолжать
});
// финализируем хэш манифеста (без строки manifest_sha256) и добавляем контрольную строку
auto manifestDigest = mh.finish(); // ubyte[32]
auto manifestHex = toHex(manifestDigest[]);
mf.writeln("manifest_sha256\t" ~ manifestHex);
mf.flush();
mf.close();
// запишем указатель "последний манифест"
writeLatestPointer(opt.storeDir, opt.filePath, mfPath);
writefln("split: %s", opt.filePath);
writefln("store: %s", opt.storeDir);
writefln("manifest: %s", mfPath);
writefln("chunks: %u, bytes: %u", cast(uint) chunkCount, cast(uint) totalBytes);
return 0;
}
// ---------- restore ----------
struct RestoreOpts
{
string storeDir;
string manifestFile; // может быть *.latest
string outFile;
}
@safe
int cmdRestore(RestoreOpts opt)
{
auto realManifest = resolveManifestPath(opt.storeDir, opt.manifestFile);
enforce(exists(realManifest), "Манифест не найден: " ~ realManifest);
string text = readText(realManifest);
auto lines = splitLines(text);
// 1) Проверка целостности: пересчитать SHA-256 всех строк ДО manifest_sha256
SHA256 mh;
string expectedHex;
foreach (ln; lines)
{
auto s = ln.strip;
if (s.startsWith("manifest_sha256"))
if (debugEnabled)
{
auto cols = s.split('\t');
enforce(cols.length == 2, "Повреждённая строка manifest_sha256");
expectedHex = cols[1];
break;
writefln("%s: Ранний возврат: остаток=%d <= минимальный размер=%d, размер чанка=%d",
__FUNCTION__, n, minSize, n);
}
// включаем в хэш строку и символ перевода строки
// (в split() выше переводы строк уже отрезаны)
mh.put(cast(const(ubyte)[]) ln);
mh.put(cast(const(ubyte)[]) "\n");
return n;
}
if (expectedHex.length)
if (n > maxSize)
{
auto gotHex = toHex(mh.finish()[]);
enforce(gotHex == expectedHex,
"Контрольная сумма манифеста не совпала:\n ожидалось: " ~ expectedHex ~ "\n получено: " ~ gotHex);
if (debugEnabled)
{
writefln("%s: Ограничение n до максимального размера: %d -> %d",
__FUNCTION__, n, maxSize);
}
n = maxSize;
}
if (n < normalSize)
{
if (debugEnabled)
{
writefln("%s: Корректировка нормального размера: %d -> %d",
__FUNCTION__, normalSize, n);
}
normalSize = n;
}
// 2) Найти секцию данных "ord\thash\tsize"
ulong fingerprint = 0;
size_t i = 0;
while (i < lines.length && !lines[i].strip.startsWith("ord"))
++i;
enforce(i < lines.length, "Не найден заголовок секции данных в манифесте");
++i;
auto dst = File(opt.outFile, "wb");
size_t count = 0;
for (; i < lines.length; ++i)
if (debugEnabled)
{
auto ln = lines[i].strip;
if (ln.length == 0 || ln[0] == '#' || ln.startsWith("manifest_sha256"))
continue;
auto cols = ln.split('\t');
enforce(cols.length == 3, "Строка манифеста повреждена: " ~ ln);
auto hashHex = cols[1];
auto cpath = chunkPath(opt.storeDir, hashHex);
enforce(exists(cpath), "Чанк не найден: " ~ cpath);
ubyte[] chunkData = readBytes(cpath);
dst.rawWrite(chunkData);
++count;
writefln("%s: Начало обработки чанка %d, остаток=%d, " ~
"минимальный размер=%d, нормальный размер=%d, максимальный размер=%d",
__FUNCTION__, chunkNumber, n, minSize, normalSize, maxSize);
}
dst.close();
writefln("restore: %s <- %s (chunks: %u)", opt.outFile, realManifest, cast(uint) count);
return 0;
// Инициализация fingerprint (отпечатка) для первых minSize байт (без проверки cut-point)
while (i < minSize)
{
fingerprint = (fingerprint << 1) + gear[src[i]];
if (debugEnabled)
{
writefln("%s: Фаза инициализации, индекс=%d, байт=%d, fingerprint=%d",
__FUNCTION__, i, src[i], fingerprint);
}
i++;
}
// Цикл до normalSize (строгая маска)
while (i < normalSize)
{
printDetails(src, fingerprint, i, maskS);
fingerprint = (fingerprint << 1) + gear[src[i]];
ulong masked = fingerprint & maskS;
if (debugEnabled)
{
writefln("%s: Фаза строгой маски, индекс=%d, байт=%d, fingerprint=%d, fingerprint & maskS=%d",
__FUNCTION__, i, src[i], fingerprint, masked);
}
if (masked == 0)
{
if (debugEnabled)
{
writefln("%s: Найдена точка разбиения на индексе=%d (строгая маска), размер чанка=%d",
__FUNCTION__, i, i);
}
return i;
}
i++;
}
// Цикл после (слабая маска)
while (i < n)
{
printDetails(src, fingerprint, i, maskL);
fingerprint = (fingerprint << 1) + gear[src[i]];
ulong masked = fingerprint & maskL;
if (debugEnabled)
{
writefln("%s: Фаза слабой маски, индекс=%d, байт=%d, fingerprint=%d, fingerprint & maskL=%d",
__FUNCTION__, i, src[i], fingerprint, masked);
}
if (masked == 0)
{
if (debugEnabled)
{
writefln("%s: Найдена точка разбиения на индексе=%d (слабая маска), размер чанка=%d",
__FUNCTION__, i, i);
}
return i;
}
i++;
}
if (debugEnabled)
{
writefln("%s: Точка разбиения не найдена, возвращается максимальный размер=%d",
__FUNCTION__, n);
}
return n; // Достигнут max
}
// ---------- CLI ----------
@safe
void printHelp(string prog)
void main()
{
writeln("Usage:");
writeln(" ",
prog, " split --store <dir> <file> [--profile text|bin] [--min N] [--avg N] [--max N] [--log-every N]");
writeln(" ", prog, " restore --store <dir> <manifest|*.latest> <out_file>");
}
// Текст без переносов строк, \n заменены на пробел
string text = "Цель науки о данных — улучшить процесс принятия решений, " ~
"основывая их на более глубоком понимании ситуации с помощью " ~
"анализа больших наборов данных. Как область деятельности " ~
"наука о данных включает в себя ряд принципов, методов " ~
"постановки задач, алгоритмов и процессов для выявления " ~
"скрытых полезных закономерностей в больших наборах данных. " ~
"Она тесно связана с глубинным анализом данных и машинным " ~
"обучением, но имеет более широкий охват. Сегодня наука о " ~
"данных управляет принятием решений практически во всех " ~
"сферах современного общества. В повседневной жизни вы " ~
"ощущаете на себе воздействие науки о данных, когда видите " ~
"отобранные специально для вас рекламные объявления, " ~
"рекомендованные фильмы и книги, ссылки на предполагаемых " ~
"друзей, отфильтрованные письма в папке со спамом, " ~
"персональные предложения от мобильных операторов и " ~
"страховых компаний. Она влияет на порядок переключения и " ~
"длительность сигналов светофоров в вашем районе, на то, как " ~
"были созданы новые лекарства, продающиеся в аптеке, и то, как " ~
"полиция вычисляет, где может потребоваться ее присутствие. " ~
"Рост использования науки о данных в обществе обусловлен " ~
"появлением больших данных и социальных сетей, увеличением " ~
"вычислительной мощности, уменьшением размеров носителей " ~
"компьютерной памяти и разработкой более эффективных " ~
"методов анализа и моделирования данных, таких как глубокое " ~
"обучение. Вместе эти факторы означают, что сейчас процесс " ~
"сбора, хранения и обработки данных стал как никогда ранее " ~
"доступен для организаций. В то же время эти технические " ~
"новшества и растущее применение науки о данных означают, что " ~
"этические проблемы, связанные с использованием данных и " ~
"личной конфиденциальностью, тоже вышли на первый план. Цель " ~
"этой книги — познакомить с наукой о данных на уровне ее " ~
"основных элементов и с той степенью погружения, которая " ~
"обеспечит принципиальное понимание вопроса. " ~
"Глава 1 очерчивает область науки о данных и дает краткую " ~
"историю ее становления и эволюции. В ней мы также " ~
"рассмотрим, почему наука о данных стала такой востребованной " ~
"сегодня, и перечислим факторы, стимулирующие ее внедрение. В " ~
"конце главы мы развенчаем несколько мифов, связанных с темой " ~
"книги. Глава 2 вводит фундаментальные понятия, относящиеся к " ~
"данным. В ней также описаны стандартные этапы проекта: " ~
"понимание бизнес-целей, начальное изучение данных, " ~
"подготовка данных, моделирование, оценка и внедрение. Глава 3 " ~
"посвящена инфраструктуре данных и проблемам, связанным с " ~
"большими данными и их интеграцией из нескольких источников. " ~
"Одна из таких типичных проблем заключается в том, что данные " ~
"в базах и хранилищах находятся на одних серверах, а " ~
"анализируются на других. Поэтому колоссальное время тратится " ~
"на перемещение больших наборов данных между этими " ~
"серверами. Глава 3 начинается с описания типичной " ~
"инфраструктуры науки о данных для организации и некоторых " ~
"свежих решений проблемы перемещения больших наборов " ~
"данных, а именно: метода машинного обучения в базе данных, " ~
"использования Hadoop для хранения и обработки данных, а также " ~
"разработки гибридных систем, в которых органично сочетаются " ~
"традиционное программное обеспечение баз данных и решения, " ~
"подобные Hadoop. Глава завершается описанием проблем, " ~
"связанных с интеграцией данных в единое представление для " ~
"последующего машинного обучения. Глава 4 знакомит читателя с " ~
"машинным обучением и объясняет некоторые из наиболее " ~
"популярных алгоритмов и моделей, включая нейронные сети, " ~
"глубокое обучение и деревья решений. В главе 5 основное " ~
"внимание уделяется использованию опыта в области машинного " ~
"обучения для решения реальных задач, приводятся примеры " ~
"анализа стандартных бизнес-проблем и того, как они могут быть " ~
"решены с помощью машинного обучения. В главе 6 " ~
"рассматриваются этические вопросы науки о данных, последние " ~
"разработки в области регулирования и некоторые из новых " ~
"вычислительных методов защиты конфиденциальности в " ~
"процессе обработки данных. Наконец, в главе 7 описаны сферы, " ~
"на которые наука о данных окажет наибольшее влияние в " ~
"ближайшем будущем, изложены принципы, позволяющие " ~
"определить, будет ли данный конкретный проект успешным.";
int main(string[] args)
{ // без @safe: getopt берёт &var
if (args.length < 2)
// Конвертация в ubyte[] (UTF-8)
ubyte[] data = cast(ubyte[]) text;
size_t totalLength = data.length;
writefln("Общая длина текста (в байтах): %d", totalLength);
// Параметры FastCDC (в байтах, адаптированы для текста)
size_t minSize = 100; // 100 байт
size_t normalSize = 200; // 200 байт (цель)
size_t maxSize = 500; // 500 байт
// Маски (для normalSize ~200 байт, log2(200) ≈ 8 бит, уровень нормализации 2)
// ulong maskS = (1UL << 8) - 1; // 8 бит: 0b11111111
// ulong maskL = (1UL << 4) - 1; // 4 бита: 0b1111
ulong maskS = 0b1111_1111;
ulong maskL = 0b1111;
// Разбиение на чанки
size_t offset = 0;
size_t chunkNumber = 1;
// Включаем отладку
bool debugEnabled = true;
writeln("Размеры и содержимое чанков:");
while (offset < totalLength)
{
printHelp(args[0]);
return 1;
}
size_t remaining = totalLength - offset;
// Включаем отладку только для первых двух чанков
debugEnabled = chunkNumber <= 2;
size_t chunkSize = fastcdc(data[offset .. $], remaining, minSize, maxSize,
normalSize, maskS, maskL, chunkNumber, debugEnabled);
switch (args[1])
{
case "split":
{
SplitOpts opt;
string store;
string profile;
size_t minS = 0, avgS = 0, maxS = 0, logEvery = 256;
// Вывод размера чанка и его содержимого
writefln("Чанк %d: %d байт", chunkNumber, chunkSize);
string chunkContent = cast(string) data[offset .. offset + chunkSize];
// writefln("Содержимое: %s\n", chunkContent.length > 50 ? chunkContent[0 .. 50] ~ "..."
// : chunkContent);
writefln("Содержимое: %s\n", chunkContent);
auto res = getopt(args,
"store", &store,
"profile", &profile,
"min", &minS,
"avg", &avgS,
"max", &maxS,
"log-every", &logEvery
);
if (res.helpWanted)
{
printHelp(args[0]);
return 0;
}
if (args.length < 3 || store.length == 0)
{
printHelp(args[0]);
return 1;
}
opt.storeDir = store;
opt.filePath = args[2];
opt.profile = profile;
if (minS)
opt.minSize = minS;
if (avgS)
opt.avgSize = avgS;
if (maxS)
opt.maxSize = maxS;
opt.logEvery = logEvery;
return cmdSplit(opt);
}
case "restore":
{
RestoreOpts opt;
string store;
auto res = getopt(args, "store", &store);
if (res.helpWanted)
{
printHelp(args[0]);
return 0;
}
if (args.length < 4 || store.length == 0)
{
printHelp(args[0]);
return 1;
}
opt.storeDir = store;
opt.manifestFile = args[2]; // можно передать *.latest
opt.outFile = args[3];
return cmdRestore(opt);
}
default:
printHelp(args[0]);
return 1;
offset += chunkSize;
chunkNumber++;
}
}

View file

@ -1,272 +0,0 @@
module fastcdc;
/**
* FastCDC (скользящее контент-зависимое разбиение).
*
* Идея: идём по данным байт за байтом, считаем rolling-hash (Gear),
* и ставим границы чанков там, где hash удовлетворяет простому условию:
* hash & mask == 0
*
* При этом действуют три порога:
* - minSize: до этой длины чанк не разрываем (слишком мелко)
* - avgSize: «целевой» средний размер; после него границы ищем активнее
* - maxSize: жёсткий максимум; если так и не нашли границу режем принудительно
*
* Благодаря контент-зависимым границам, маленькие вставки/удаления
* меняют только локальные чанки; большинство остальных переиспользуется.
*/
/// Параметры FastCDC. Рекомендуется avgSize как степень двойки (например, 64 KiB).
struct FastCDCParams
{
/// Минимальная длина чанка: пока длина меньше — границы не ставим.
size_t minSize = 8 * 1024;
/// Целевой средний размер чанка (желательно 2^k: 32, 64, 128 KiB и т.п.).
size_t avgSize = 64 * 1024;
/// Жёсткий максимум длины чанка: достигли — режем независимо от хэша.
size_t maxSize = 256 * 1024;
/**
* Нормализация границ (делает параметры самосогласованными):
* - minSize >= 4 KiB
* - avgSize > minSize (если нет удваиваем)
* - maxSize > avgSize (если нет умножаем на 4)
*
* @safe nothrow @nogc чтобы можно было вызывать из безопасных функций
* без аллокаций/исключений.
*/
@safe nothrow @nogc
void normalize()
{
enum ki = 1024;
if (minSize < 4 * ki)
minSize = 4 * ki;
if (avgSize <= minSize)
avgSize = minSize * 2;
if (maxSize <= avgSize)
maxSize = avgSize * 4;
}
}
/// Диапазон чанка: [start .. start+len) в исходном буфере.
struct ChunkRange
{
size_t start; // смещение начала чанка
size_t len; // длина чанка в байтах
}
/**
* Таблица для Gear-rolling-hash: 256 псевдослучайных 32-битных констант.
* Хэш обновляется так: h = (h << 1) + GEAR[byte].
* Константы фиксированы детерминированные границы при одинаковых параметрах.
*/
private immutable uint[256] GEAR = [
0x4f1bbcdc, 0xe47c2b1a, 0x1a16b407, 0xa88f9d43, 0x33b0b5c5, 0x0f0b1192,
0xb1c2e5b8, 0xc6f5a2a9, 0x7d1ea04c, 0x26358f23, 0x8f9a7902, 0x5ab7d6ee,
0x2f6d3a8c, 0x9e13c540, 0x4d7c8b99, 0xf3a1d2b1, 0x0b7d4c62, 0x81f2a5d3,
0x19a8b604, 0x6cc7e190, 0x559e43a1, 0xd2f01937, 0x7a53cc4f, 0x0c1d5e66,
0x3b7f6a22, 0x99d104b7, 0x4aa7f9e1, 0x2ce3b8c0, 0x6a8f73d4, 0xe1b2c9a8,
0x57c04b13, 0xa4e91572, 0x13f7c2aa, 0x8b1c0f5e, 0x5e6a92c1, 0x0af41937,
0x7fe0bd54, 0x26b3e71a, 0x942d6c83, 0x3c51a0ef, 0xd57f2b33, 0x61a4cc09,
0x0d9b8a71, 0xb7e50f46, 0x48a3d1f0, 0x2f1e6cb2, 0x73cd98a5, 0xe92a13c9,
0xa1c7f02e, 0x5b0e6a97, 0x0c8f2d31, 0xd1a47b66, 0x6fe3b920, 0x20b9d4a1,
0x9a5c0f3d, 0x4e81a2c7, 0xf02b5934, 0x1bc7d8a2, 0x8e0a64f1, 0x37d4b20c,
0x6c09f5d3, 0xa2391e84, 0x5f7ab0e2, 0x0b1c6d57, 0x7c3f9a15, 0x12ad54e3,
0x8b6f0c2d, 0x45e1d7a9, 0x2af39b60, 0x9c07e4d1, 0x3d5a81b2, 0xe6c21458,
0xd9a03f1c, 0x64b7e0a3, 0x0ea19c76, 0xb2d5480f, 0x49f3a7d5, 0x21c58e92,
0x75ae39c1, 0xed1046ab, 0xa8c3f12d, 0x5c0e7b94, 0x0d8f2e31, 0xd4a57c66,
0x6ee4ba20, 0x23bad5a1, 0x985d0f3d, 0x4f82a3c7, 0xf12c5a34, 0x1ac8d9a2,
0x8f0b65f1, 0x36d5b30c, 0x6b0af6d3, 0xa33a1f84, 0x5e7bb1e2, 0x0a1d6e57,
0x7d409b15, 0x11ae55e3, 0x8a700d2d, 0x44e2d8a9, 0x2bf49c60, 0x9d08e5d1,
0x3c5b82b2, 0xe7c31558, 0xd8a1401c, 0x65b8e1a3, 0x0fa29d76, 0xb3d6490f,
0x48f4a8d5, 0x22c68f92, 0x74af3ac1, 0xec1147ab, 0xa9c4f22d, 0x5d0f7c94,
0x0e902f31, 0xd5a67d66, 0x6de5bb20, 0x24bbd6a1, 0x975e103d, 0x4e83a4c7,
0xf22d5b34, 0x1bc9daa2, 0x8e0c66f1, 0x35d6b40c, 0x6a0bf7d3, 0xa23b2084,
0x5f7cb2e2, 0x0b1e6f57, 0x7c419c15, 0x10af56e3, 0x8b710e2d, 0x45e3d9a9,
0x2af59d60, 0x9c09e6d1, 0x3d5c83b2, 0xe6c41658, 0xd9a2411c, 0x64b9e2a3,
0x0ea39e76, 0xb2d74a0f, 0x49f5a9d5, 0x23c79092, 0x75b03bc1, 0xed1248ab,
0xa8c5f32d, 0x5c107d94, 0x0d913031, 0xd4a77e66, 0x6ee6bc20, 0x25bcd7a1,
0x985f113d, 0x4f84a5c7, 0xf12e5c34, 0x1acadba2, 0x8f0d67f1, 0x36d7b50c,
0x6b0cf8d3, 0xa33c2184, 0x5e7db3e2, 0x0a1f7057, 0x7d429d15, 0x11b057e3,
0x8a720f2d, 0x44e4daa9, 0x2bf69e60, 0x9d0ae7d1, 0x3c5d84b2, 0xe7c51758,
0xd8a3421c, 0x65bae3a3, 0x0fa49f76, 0xb3d84b0f, 0x48f6aad5, 0x22c89192,
0x74b13cc1, 0xec1349ab, 0xa9c6f42d, 0x5d117e94, 0x0e923131, 0xd5a87f66,
0x6de7bd20, 0x24bdd8a1, 0x9750123d, 0x4e85a6c7, 0xf22f5d34, 0x1bcbdca2,
0x8e0e68f1, 0x35d8b60c, 0x6a0df9d3, 0xa23d2284, 0x5f7eb4e2, 0x0b207157,
0x7c439e15, 0x10b158e3, 0x8b73102d, 0x45e5dba9, 0x2af79f60, 0x9c0be8d1,
0x3d5e85b2, 0xe6c61858, 0xd9a4431c, 0x64bbe4a3, 0x0ea5a076, 0xb2d94c0f,
0x49f7abd5, 0x23c99292, 0x75b23dc1, 0xed144aab, 0xa8c7f52d, 0x5c127f94,
0x0d933231, 0xd4a98066, 0x6ee8be20, 0x25bed9a1, 0x9861133d, 0x4f86a7c7,
0xf1305e34, 0x1acca0a2, 0x8f0f69f1, 0x36d9b70c, 0x6b0efaD3, 0xa33e2384,
0x5e7fb5e2, 0x0a217257, 0x7d449f15, 0x11b259e3, 0x8a74112d, 0x44e6dca9,
0x2bf8a060, 0x9d0ce9d1, 0x3c5f86b2, 0xe7c71958, 0xd8a5441c, 0x65bce5a3,
0x0fa6a176, 0xb3da4d0f, 0x48f8acd5, 0x22ca9392, 0x74b33ec1, 0xec154bab
];
/// Обновление Gear-хэша одним байтом. @safe @nothrow @nogc — без аллокаций/исключений.
@safe nothrow @nogc
private uint gearUpdate(uint h, ubyte b)
{
// Побитовый сдвиг + добавление таблицы → быстрый, но «живой» rolling-hash.
return (h << 1) + GEAR[b];
}
/**
* Ближайшая вниз степень двойки для x (>=1).
* Нужна, чтобы построить маски вида (2^k - 1) и (2^(k+1) - 1).
* При таких масках условие (hash & mask) == 0 имеет ожидаемый период ~2^k.
*/
@safe nothrow @nogc
private size_t floorPow2(size_t x)
{
size_t p = 1;
while ((p << 1) != 0 && (p << 1) <= x)
p <<= 1;
return p;
}
/**
* Batch-API: режет весь буфер и возвращает список чанков.
* Удобно, если объём данных умеренный и важна простота.
*
* Сложность: O(n), где n длина data.
*/
@safe
ChunkRange[] chunkify(const(ubyte)[] data, FastCDCParams params = FastCDCParams.init)
{
FastCDCParams p = params;
p.normalize(); // приводим пороги в адекватные границы
ChunkRange[] chunks; // итоговый список диапазонов
// Необязательный pre-reserve: уменьшает реаллокации; можно удалить, если не нужно.
chunks.reserve(data.length / (p.avgSize ? p.avgSize : 1) + 1);
// Строим две маски:
// - maskN (normal): 2^k - 1 → «частота» границ ~ avgSize
// - maskE (early): 2^(k+1) - 1 → «строже», реже до avgSize (не режем слишком рано)
const size_t avgPow2 = floorPow2(p.avgSize);
immutable uint maskN = cast(uint)(avgPow2 - 1);
immutable uint maskE = cast(uint)(((avgPow2 << 1) != 0) ? ((avgPow2 << 1) - 1) : maskN);
size_t start = 0; // начало текущего потенциального чанка
uint rolling = 0; // текущее значение rolling-hash
foreach (i, b; data) // идём по байтам
{
rolling = gearUpdate(rolling, b); // обновили rolling-hash
const size_t clen = (i + 1) - start; // текущая длина кандидат-чанка
// 1) До minSize границы запрещены — продолжаем копить байты.
if (clen < p.minSize)
continue;
// 2) Если превысили maxSize — принудительно режем тут.
if (clen >= p.maxSize)
{
chunks ~= ChunkRange(start, clen);
start = i + 1; // новый чанк начинается со следующего байта
rolling = 0; // сбрасываем хэш (независимая последовательность)
continue;
}
// 3) Зона «ранняя» (min..avg): применяем более строгую маску, чтобы
// не нарезать слишком короткие чанки.
if (clen < p.avgSize)
{
if ((rolling & maskE) == 0) // редкое совпадение → ставим границу
{
chunks ~= ChunkRange(start, clen);
start = i + 1;
rolling = 0;
}
continue; // иначе продолжаем наращивать чанк
}
// 4) Зона «нормальная» (avg..max): используем обычную маску — средний размер ≈ avgSize.
if ((rolling & maskN) == 0)
{
chunks ~= ChunkRange(start, clen);
start = i + 1;
rolling = 0;
}
}
// 5) Хвост: если что-то осталось — это последний чанк.
if (start < data.length)
chunks ~= ChunkRange(start, data.length - start);
return chunks;
}
/**
* Стримовый API: вызывает sink(start,len) для каждого найденного чанка.
*
* Плюсы:
* - не аллоцирует список чанков (можно сделать @nogc-путь);
* - удобно сразу считать хэш чанка и писать в БД.
*
* Контракт sink:
* - возвращает 0 «продолжать»;
* - возвращает != 0 «остановить» (функция вернёт количество уже эмитнутых чанков).
*/
@safe
size_t processStream(const(ubyte)[] data, FastCDCParams params,
scope int delegate(size_t, size_t) @safe sink)
{
FastCDCParams p = params;
p.normalize();
const size_t avgPow2 = floorPow2(p.avgSize);
immutable uint maskN = cast(uint)(avgPow2 - 1);
immutable uint maskE = cast(uint)(((avgPow2 << 1) != 0) ? ((avgPow2 << 1) - 1) : maskN);
size_t start = 0;
uint rolling = 0;
size_t count = 0; // числим эмитнутые чанки
foreach (i, b; data)
{
rolling = gearUpdate(rolling, b);
const size_t clen = (i + 1) - start;
// До minSize границы не допускаются
if (clen < p.minSize)
continue;
// Решение «резать/не резать» для текущей позиции
bool cut = false;
if (clen >= p.maxSize) // принудительный разрыв на maxSize
cut = true;
else if (clen < p.avgSize) // раннее окно — строгая маска
cut = ((rolling & maskE) == 0);
else // нормальное окно — обычная маска
cut = ((rolling & maskN) == 0);
if (cut)
{
// Отдаём чанк потребителю. Он может попросить остановиться (!=0).
if (sink(start, clen) != 0)
{
++count; // этот чанк уже отдан — учитываем
return count; // и выходим
}
++count;
start = i + 1; // следующий чанк начинается со следующего байта
rolling = 0; // сбрасываем rolling-hash
}
}
// Хвостовой чанк (если есть): отдаём целиком.
if (start < data.length)
{
if (sink(start, data.length - start) != 0)
{
++count; // учли последний чанк
return count; // останов по сигналу sink
}
++count;
}
return count;
}

66
source/gear.d Normal file
View file

@ -0,0 +1,66 @@
immutable ulong[256] gear = [
0x2722039f43c57a70, 0x338c1bd5b7ac5204, 0xf9f2c73ff33c98c0, 0x7dee12e6cd31cb32,
0x9688335e0f2decfd, 0x5307003c8e60b963, 0xfd2a2848eb358095, 0xc3614773074ee6b7,
0x6e35235234b6ed0a, 0x9d4cfa9d8e3850cc, 0xaa1b3d8af8ad86bd, 0x79c6d2e28bfb333d,
0x3df08966a00c33ec, 0xfd58bbf83f38c690, 0x5ef9ee9a4552545b, 0x099192a7e5599bdc,
0xa8f2419947f21017, 0xd6a03d010f2fda7c, 0x1fe53de04074fc20, 0x75b5aff7c66605f8,
0x1a94b7484bf509a9, 0xbf2e371a53466ded, 0xedcf13f8eb0f0fdf, 0xfba81285ead7dafe,
0x839fb29274557fa5, 0xeefe64b15cc7f7f0, 0x7d15f8e862726515, 0x59b416e43cca2adc,
0x9c2c925dcde12d4a, 0xf3df9373c3e63a07, 0x747cb5ec08ffa4ef, 0x26c93138f3f19b29,
0xcdade11723bd59ed, 0xc7a6a7d0c18642cb, 0x88c2976f22f5d084, 0x7c48f54cdaf480fe,
0x91ea7c7fd3d06d54, 0xed3e31236e8c9366, 0xa16da2234f420bc4, 0x5ee6136449d30974,
0xe32a921921181e16, 0xa6ab2fb8c7212257, 0x754a8a581ce819ca, 0x22b2de3e7c7a2f57,
0xd2773285e49b9160, 0x19b0449384554129, 0x145e7d2c46da460e, 0xdd720d0e79a3615d,
0x621ae4f0ad576223, 0x4f6da235cc25d5c9, 0x6d6d39e005d67437, 0x5839c8f3d2f71122,
0x691a668bc7f5c153, 0xb2eb484f8546c61d, 0x7955c346c34cbcdc, 0x413c2a0ba6fd0a88,
0x29ad3d418323592b, 0xb7d04d0abf3f8d50, 0x76e742f91dfc77ea, 0x8a5c80d1a0d5ff5c,
0xce1aa9b0bdd16adc, 0x74e4bd6f412c8186, 0xbf1dddc8f63dfc08, 0x11dcb84c1b5c32cb,
0x3320ed259fc0d8c0, 0x13dbd4c934c58e01, 0x344b61dd3741a9f9, 0x935861bea84a6f81,
0xaf70eea3844052f9, 0xc0a83f93799c2e81, 0xdd23b2a943a5af16, 0x05b4efd89b3e818b,
0x75b2a3d0fe099aec, 0x5aab5599ae580c37, 0xe9b64238ed626a6b, 0xb63f47c31c31ec1d,
0x0b50ee03c3425dde, 0xf287ebed924249f6, 0xe09eee62604318c4, 0x0d334cb1bd82bc13,
0xc41abf3d95b18620, 0x869c3bc45e2c9edf, 0x526de53484e093c7, 0xc640fee4784fd9ce,
0x761637787d81c0ea, 0x817bf175cb17e903, 0x94a4846f1158f988, 0x99c254e5f8e698e0,
0xa4623d4d1b76352e, 0x326dae4493475c3a, 0xed2944be79511208, 0x163a0a9b65f40339,
0x336489f8c2f6190c, 0x670d217f8e6bee33, 0x662e19c285c5a4a1, 0xcab8f4512d0b251a,
0x61d4476812ed1017, 0x0ec77209307430af, 0x20a94905901093dc, 0xaa9fe2cae9ffa699,
0xc75f757de6c045dc, 0x141ef38478656459, 0x9b3ce9c4e3dd7858, 0x3ab62b9aa45a3d0d,
0x61a89423e18e5e68, 0x3802972ecadf592d, 0xcfc85c5724ff3af8, 0x381ee916e97a628a,
0x2fa2c37a040e488a, 0x9813a505b4ca4036, 0xc4254f1aaf7b2f42, 0xe8a0720b79a1188d,
0xe663a71adb5d53e3, 0x6e3b5927934102af, 0xbd8c502741b1fcb1, 0x1af6fa2fb1d2e5a6,
0xc88d367a79d06f5d, 0x29fe7cdab66530d9, 0x34bef2ebe612d95f, 0x9ab6977a57db1fa2,
0x73774fc29deac09a, 0x7832f37495fd28fb, 0x1559a3badfbd42a6, 0x7e6831522a50d2bc,
0xddb8564f3aafe3b7, 0x86acb9eca71bc09d, 0x21b0a9469727d4fc, 0x26d3b66f525ebcab,
0x77e3fd126fd97e3a, 0x5306b81a9fe2a92e, 0x7292138f116d8911, 0x285b466c9939b076,
0x40527805d9a4379d, 0x8986c05119c7ca1e, 0x6a7890c402303c31, 0xb1b109dc109405bc,
0x1d71f3997b288f30, 0xfa203ff4dc9ea72c, 0x8ae3eea975cc92da, 0x3468e4305eabb928,
0xd79c37e720467df1, 0x011e6490c0f832d2, 0x29ce2ada8509647a, 0xb4e325b9f3ba783c,
0xa812ca4fad720763, 0x0cdf098645ccb476, 0xf6b47e21637fcd76, 0x3597f48148a297de,
0x5875212868ab81ec, 0x1ea36243940596bb, 0xfd93ac7c39a27586, 0xabb09b0f803a7214,
0x8cc8ec1ea21a16af, 0x824a0db50ae906d1, 0x3d972fb701ca3e70, 0xda60d493e9a20bd0,
0x97d282f6bda26087, 0x9bc8f7842af296d0, 0x14804a1663a0cf7e, 0x3b71cc25885e75f3,
0x131adc05e336042b, 0x566aa36d26eee86c, 0x97d4c4d4fd4b0dd1, 0xd2407b1485c7bee1,
0xcad613e7b92e6df1, 0xe3ceccd99d975088, 0x99e6b93ff96a2636, 0x1ad75dbed057f0d0,
0x5e3ba609dd100c6e, 0x9c5efa00b33a18f3, 0xad89369e692bdb28, 0xf7a546fca26d1d7d,
0x5813db1fe943575f, 0x24c3467f03a144ae, 0xc892f2ce492cb7c8, 0xc44672263508d34b,
0xd400e1c0a5734a40, 0x3ca24ee74bf8e84f, 0xd83bd4e907c351a5, 0xe142297005fa9aa8,
0x0f6d796cf68abda0, 0x6c8e25bc6d9ae2e8, 0xccc235f322a42cf3, 0xabaf39cea8ca450c,
0x02b9cdf615a0d7b6, 0x8aaf7d8b55d4dc39, 0xbe2c2bc6ef13c6c5, 0x6ad98aa4a4bc610f,
0x1051a62ac2a2b434, 0xbd167e6eba260d35, 0xb9b86ac04ac4f811, 0xabe8a6453e196739,
0x439ff734b19246b4, 0xcea324040c9e8981, 0x87f55cf1035e1a22, 0xa227d679c33597f9,
0xbf4d654b6cdd0015, 0xc0302ec55f87a46e, 0xed32173466c70a83, 0x8ceb757b648d2bf2,
0x1873757a6d17446b, 0xeb0f366fea62e77e, 0x145aa2795d34dd93, 0x2fc378be4c471db0,
0x6d1274fb8f6364a2, 0x602a56fd1cc36728, 0x5f8aa6e0c892b4b5, 0x33e2c5653d8b1ad6,
0x1f6c8b2a004714f4, 0x4042b98d54acbfef, 0x4606386f11f6456f, 0xf56bd21a8a35c540,
0xd2b23c57b3718e1f, 0x94726832fe96e61d, 0xa225b072752a823b, 0x0bd957cf585f8cda,
0x533d819bb30b4221, 0xda0f9cff9a0115fa, 0xd14de3b8fe3354ea, 0xa96328e96d9364c0,
0x9078dc0eff2676ab, 0x22585cd4521c6210, 0x5903254df4e402a5, 0x1b54b71b55ae697a,
0xb899b86756b2aa39, 0x5d5d2dd5cd0bce8b, 0x7b3a78a4a0662015, 0xa9fbfc7678fc7931,
0xa732d694f6ab64a0, 0x9fc960e7db3e9716, 0x76c765948f3c2ba5, 0x076a509dca2a4349,
0xca5bfc5973661e59, 0x454ec4d49bddd45d, 0x56115e001997cee2, 0xd689eb8926051c7f,
0xf50df8ca9c355e3f, 0x88a375a9f0492a69, 0xe059fd001d50439a, 0x765c5d6f66d5e788,
0xaf57f4eea178f896, 0x06e8cca68730fbbd, 0xb7b1f6f86904ce4e, 0x3c3b10b0c08cf0bf,
0x1e0e310524778bd4, 0xd65d7cd93cde7c69, 0x18543b187c77fcf3, 0x180f6cdd1af3a60a,
0xe1cd4c2bc3656704, 0x218fdfc5aa282d00, 0x844eeaf2e439b242, 0x05df1a59e415b4c6,
0x14abdd3ace097c2c, 0x7f3b0705b04b14d4, 0xf69c57f60180332b, 0x165fc3f0e65db80f
];

48
tools/gen.d Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/rdmd
module tools.gen;
import std.stdio;
import std.random : Random, unpredictableSeed, uniform;
void main()
{
enum N = 256;
ulong[N] gear;
auto rng = Random(unpredictableSeed);
bool[ulong] seen;
ulong[] vals;
vals.reserve(N);
while (vals.length < N)
{
const v = uniform!ulong(rng);
if (v in seen)
continue;
seen[v] = true;
vals ~= v;
}
gear[] = vals[0 .. N];
writeln("immutable ulong[256] gear = [");
foreach (i, v; gear)
{
// Индент только в начале каждой строки
if (i % 4 == 0)
write("\t");
writef("0x%016x", v);
if (i != N - 1) // не ставим запятую после последнего
write(",");
// Перенос после каждого 4-го элемента или в самом конце
if ((i + 1) % 4 == 0 || i == N - 1)
writeln();
else
write(" ");
}
writeln("];");
}