Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Суть решения — в использовании обычного индексного массива. Для каждого списка создаётся массив размером в миллиард.
Объём данных не должен оказывать значительного влияния на трудоёмкость разработки. Основная трудоёмкость разработки, как правило, связана со сложностью алгоритма обработки данных, а не с их количеством. Заранее зная фактический объём данных, достаточно разработать код, который работает на небольших данных, а затем его можно применить к требуемому объёму.
Главное, как можно раньше (до начала разработки), определить правильный подход к задаче. Но это вопрос не трудоёмкости, а профпригодности — то есть, матчасть надо изучать заранее, а разрабатывать быстро.
У вас задача на обработку «больших объемов данных», и вы ее решаете, загружая все данные в память? А когда памяти не хватило — просто ее добавили?
Внезапно у вас идентификаторы перестали быть числовыми, и что теперь?
Есть алгоритмы посложнее, но побыстрее
Если задача может быть решена простым добавлением памяти — в этом нет ничего плохого, это хорошо. Значит в запасе есть ещё один простой механизм решения.
Внезапно тип индентификатора измениться не может (бывает конечно, но о-очень редко, как правило из-за взаимного недопонимания с Заказчиком).
Здесь вы меня заинтриговали. Что вы имеете в виду?
Вопрос в том, как решить вашу задачу, не добавляя памяти.
Я имею в виду, что ваше решение хорошо работает до тех пор, пока идентификаторы числовые. (Идентификаторы стали не числовые) Что вы будете делать в этом случае?
Допустим всё увеличилось в 10 раз.
После каждой подкачки прогоняется файл связей (каждый раз все 10 миллиардов).
За ночь, вероятно, подсчёт бы выполнился.
Для решения надо подготовить хеш-таблицы, где GUID однозначно сопоставляется с числом (это самая затратная по времени операция, её следует продумать). Затем решаем задачу уже известным способом.
А если в тысячу?
А это точно допустимое время выполнения? А точно нет другого способа оптимизировать алгоритм?
Во сколько раз вы только что увеличили требования по памяти? А, главное, зачем вам сопоставлять гуиды с числами?
Это однозначно указывает на то, что данные взяты из промышленной СУРБД.
СУРБД, соответствующих вашим запросам, на текущий момент ещё не разработано
Одна ночь — это о-очень хороший результат. Обычно, если удаётся уложиться в это время — дальше уже никто не парится, даже если есть известные пути оптимизации.
А кому действительно нужен реалтайм — тот вынужден платить о-очень много за оборудоване и разработку, и процент успешного выхода не 80/20, а 20/80.
Требования по памяти на рабочий процесс не увеличилось, совсем. Так как хэш-индекс (именно о нём идёт речь при сопоставлении GUID-а с числом) придётся готовить заранее. [...] Другое дело подготовка хэш-индекса на связной список — скорость поиска в ассоциативном массиве на порядки медленнее, чем скорость обращения по индексу в массиве из примера. [...] Когда хэш-индексы подготовлены — отлично будет работать пример из публикации.
Чисто теоритически есть ещё подход — разработать хэш-функцию, которая на заданном числовом интервале, даст однозначное уникальное преобразование GUID-а в число. Но, к сожалению, вероятность успешной разработки такой функции равна нулю.
Лично мне это никак не указывает. Потому что если вы работаете с СУБД, то совершенно не понятно, зачем вы это делаете не в ней.
Я не очень понимаю, что именно вы имеете в виду. Вы не могли бы показать на примере?
Вы все еще считаете, что объем данных не влияет на трудоемкость?
Плохокодить я не буду. А работа с хэш-таблицами и ассоциативными массивами хорошо описана. Если-бы мне было известно хорошее решение по работе с нечисловыми идентификаторами, то в публикации было бы показано оно.
Утверждение дано в самом начале публикации «Главное, как можно раньше (до начала разработки), определить правильный подход к задаче».
ЗЫ Ваши вопросы перестали быть интересными. Давайте закроем дискуссию. Или переведём её в другой формат (но только чтобы было действительно интересно).
Ваше решение всегда выводит данные только по одному типу муравьев. Так и должно быть, или это его недоработка?
int filtrAntType = 5; // дефолтный фильтр - солдат
Как именно должен выглядеть результат работы (вывод)?
Как в выводе должны выглядеть результаты для муравьев, живущих в более чем одной клетке (раз уж вы утверждаете, что это возможно)?
Откуда в реальности поступают исходные данные, и до какой степени ими можно манипулировать (напоминаю, что вы утверждали, что данные выгружаются из СУБД)?
Можно ли рассчитывать на то, что идентификаторы (муравьев и клеток) целочисленные и последовательные (в значении contiguous)?
Можно выбрать любой тип заданием параметра: anthill-makeresult.exe 4 (1- царица, 2 — личинка, 3 — нянька, 4 — рабочий, 5 — солдат)
Для простоты и переносимости использован CSV.
Отношение многие-ко-многим не накладывает количественных ограничений на связи. Главное чтобы идентификаторы в списках были уникальными и пары идентификаторов в таблице связи были уникальными, но за это отвечает СУРБД или утилита подготовки тестовых данных (просто утилита не перемешивает идентификаторы — пусть это вас не смущает, ни пропуски ни последовательность идентификаторов роли не играют).
Хранятся списки в таблицах СУРБД или в CSV файлах значения не имеет.
Данные целочисленные, последовательность и пропуски значения не имеют.
Я так понимаю, что данные групируются по «регионам» муравейника — ок. Что выводится для каждого муравья?
Как в выводе должны выглядеть муравьи, привязанные к нескольким комнатам? Например, если муравей привязан к нескольким комнатам, и эти комнаты в разных регионах — он должен появиться в группе каждого из этих регионов?
Например, могут ли они быть заранее отсортированными по нужному мне параметру? (и да, я ожидаю ответ «да, могут», потому что я с трудом могу себе представить реальную ситуацию, когда это невозможно)
При этом, описанный в публикации подход, элементарно вылетает в трубу.
Непонятно, зачем брать структуру, изначально ориентированную, на целочисленные идентификаторы и исследовать её просто заменив эти идентификаторы на GUID-ы.
Есть действительно актуальные задачи, странно почему вы их избегаете.
1. Что такое «быстрый поиск»? Поиск по чему? По ключу? По значению?
2. Проще говоря, идеальная хэш-функция. Теоретически возможна в том случае, когда размеры множеств идентичны, но как вы планируете гарантировать это условие?
Универсальный, возможно, ориентированный на рассмтриваемую задачу, если предусмотреть в нём индексирование по прикладным данным.
Практически, такую хэш-функцию можно разработать, если предусмотреть в ней так-же индексирование по прикладным данным.
Я еще раз спрашиваю: поиск по ключу или по значению?
Я еще раз спрашиваю: поиск по ключу или по значению?
Предположим, что один проход по ассоциативному массиву в миллиард, равен одной секунде.
Но, если к этой задаче подключить архитектуру (например разделить процесс),
то можно значительно улучшить время
Что такое «один проход»?
К какой задаче? Оптимизации одного обращения или оптимизации миллиона обращений?
«Один проход» — это «лукап» по всему массиву (в миллиард).
Речь идёт об оптимизации миллиарда обращений.
К счастью, сам по себе словарь прекрасно выдерживает параллельные чтения (если нет параллельных записей), поэтому вы можете как угодно оптимизировать доступ к нему.
Когда словарь лежит на диске (а миллиард GUIDов в память не лезет), то с параллельным чтением могут возникнуть сложности.
И задача оптимизации параллельных обращений вполне может оказаться частью структуры данных.
Я продолжаю не понимать, что вы пытаетесь сделать. Получить значение по конкретному ключу? Получить все ключи? Получить все значения?
Для разных прикладных задач нужны разные структуры данных, зачем в одну-то все запихивать?
HashSet<Guid>
, все прочее — O(1), за исключением записи о муравьях в ячейке, там линейное от максимального количества муравьев в какой-либо ячейкеOrdinal
): 3:15StringComparer.Ordinal
).В общем, новая версия
Два списка должны быть отсортированны в «неубывающую последовательность» до начала обработки: cells и antsToCells(отсортированный по cells). Это условие лежит в основе работы алгоритма.
В память (HashSet) полностью помещается ants
Во сколько вы оцениваете потребность в памяти (сколько RAM должно быть на компе), чтобы обеспечить обработку миллиарда записей?
Как вы будете решать задачу, для 10 миллиардов?
Допустим есть такой поставщик/источник данных (140 Гб в миллиарде записей). Сможет ли этот поставщик данных поставлять отсортированные данные?
А, если нет (сервера опечатаны)? Как вы будете решать такую задачу?
Как вы будете решать задачу, для 10 миллиардов?
Аналогично.
Скорее всего — через внешнюю сортировку
А во сколько вы оцениваете время выпонения полной сортировки на миллиарде записей?
можно сделать допущение, что все необходимые структуры всегда помещаются в память
Если в памяти — то за единицы минут плюс время чтения с диска и записи на диск
using System;
using System.Diagnostics;
namespace TimeTestBillion
{
class Program
{
static void Main(string[] args)
{
long q = 0;
Stopwatch t = new Stopwatch();
t.Start();
for(int i = 0; i < 1000000000; i++)
{
for (int k = 0; k < 10; k++)
{
q++;
}
}
t.Stop();
Console.WriteLine(string.Format("Time: {0} Value: {1}",t.Elapsed.ToString(@"mm\:ss\.fff"), q));
}
}
}
Следовательно, на выполнение вложенного цикла миллиард в миллиарде, должно быть затрачено 650 миллионов секунд.
А зачем нужен вложенный цикл «миллиард в миллиарде»?
Вы можете предложить алгоритм сортировки без организации вложенных циклов?
Скорее всего — через внешнюю сортировку
А во сколько вы оцениваете время выпонения полной сортировки на миллиарде записей?
Вы зачем-то меряете решение с алгоритмической сложностью O(n2), хотя известно, что сортировка — это O(n log n).
А вот теперь — печальный факт.
insert Ants (type)
output inserted.Id into @ant
values (@type) --последовательно увеличивается
insert Cells (area)
output inserted.Id into @cell
values (0) --а это не важно, мог бы и случайный генерить
insert Links
select antId, cellId
from @ant cross join @cell
Как-то так
Не понимаю смысла формирования GUID-ов разного типа для муравьёв и для ячеек:
При подготовке результата (area-071, area-153,… ) всегда присутствует только по одной записи в каждом файле. Это недоработка?
для ячеек удобнее использовать последовательные. Это критично только в генерации.
У меня такого поведения не наблюдается. Какого размера у вас стартовый массив данных?
Не понимаю, вы можете пояснить в чём удобство? (По коду я не понял, а комментарии отсутствуют)
Пока 1000. Я устанавливал разные типы (например 255) — результат — всегда одна запись на один файл.
Для муравьев можно использовать случайные, а для ячеек удобнее использовать последовательные. Это критично только в генерации, парсер этот факт игнорирует
Для парсера важно, что и в файле cells, и в antsToCells имена ячеек идут в лексикографическом порядке (Guid не убывают).
Если для парсера важно, чтобы GUID-ы были «ненастоящими» и последовательными
Нет, это не важно. Важно, чтобы последовательность была неубывающей.
В чем их подтасованность?
Что вам мешает на этапе генерации провести этот «препроцессинг» на основе «честных» GUID-ов (конечно это немного усложнит генератор).
но скорость генерации падала даже не как n log n от объема
Скорее наоборот — препроцессинг невозможен.
Или привидите пример, как вы собираетесь выгружать отсортированные данные такого объёма из, например, СУРБД?
Как-как, по индексу, конечно.
И да, если это проблема, то почему вы согласились с тем, что входные данные могут быть отсортированными?
ORDER BY GUID_ID по таблице в миллиард записей? Как вам удаётся такие индексы строить?
Я вовсе не соглашался, я просто переубеждать вас не стал.
Давайте проводить честный эксперимент и начнём с тех проблем, которые могут возникнуть в реальной жизни
Обычный такой индекс на БД
Ну нет, так не работает
Какие проблемы у вас могут возникнуть «в реальной жизни»?
Индекс для сортировки не поможет, так как ресурсы, требуемые для запроса-вывода, значительно превышают ресурсы любого мыслимого сервера.
Пока, возникли проблемы с простой сортировкой списка.
Ээээ… то есть вывести данные из таблицы можно, а вывести данные из индекса (который сопоставимого размера) — не хватает ресурсов? Но как?
Пока, возникли проблемы с простой сортировкой списка.
В реальной жизни нет проблем с сортировкой списка, потому что в реальной жизни сортировка списка происходит за рамками задачи.
О каких индексах
о каких рамках
Сделайте простой сортированный список на GUID-ах и О'кей.
Но вы утверждаете, что проблема в скорости.
Хорошо, предложите другой подход.
Я повторяю — решение этой проблемы мне неизвестно.
Другой подход к чему?
Какой проблемы?
Так уже решил же
Вы даже не реализовали сортированного списка тестовых данных для «честных» GUID-ов.
Я даже не приступал к тестированию вашего решения на больших данных, потому что это бессмысленно, ваше решение имеет ограничение на размер списка.
HashSet
). На ста миллионах записей вся программа потребляла 23-25Мб в пике. Я считаю, это неплохой показатель.ваше решение имеет ограничение на размер списка.
Всего лишь ещё одно подтверждение того, что объём данных влияет на трудоёмкость
Если для парсера важно, чтобы GUID-ы были «ненастоящими» и последовательными
Пока 1000. Я устанавливал разные типы (например 255) — результат — всегда одна запись на один файл.
Всего муравьёв типа 255 в файле ants — 273
Поселённых окажется около 40, что соответствует вашим результатам
Но я с удовольствием послушаю про алгоритм, который бы генерил полный набор данных со случайным распределением.
Я вообще не понимаю, зачем вам нужно здесь случайное распределение.
Муравьёв — 1000, ячеек — 1000, связей — 1000.
Вы сдаёте систему. Ни один приёмщик вам акты не подпишет.
Какой сценарий вам интереснее? Мне кажется, что второй правильнее.
когда я сдаю систему, есть ПМИ
Я думаю, что было-бы интереснее и правильнее использовать подход, когда для каждого муравья есть ячейка и, некоторые муравьи, могут жить в нескольких ячейках (то есть список связей больше количества муравьёв).
Интересно посмотреть на ПМИ, где прописано, что зависимости получаются случайным образом и не проверяются (где вы таких Заказчиков находите?).
Разве вам не знакома ситуация, когда завтра/на следующей неделе должна сдаваться система,
но к вам подходит ПМ и ставит в известность, что с Заказчиком подписаны «небольшие» изменения, «не влияющие на сроки», которые надо срочно доработать.
Окей, на какие множители вы согласны?
В такой ситуации полезно пойти к начальнику
Меня устроят любые.
В такой ситуации, приоритет контракта с Заказчиком и наличие мотивированного ПМ-а в этом контракте, для любого начальника, на порядок (или два) выше личного недовольства, пусть даже гениального, разработчика.
Но они вас почему-то не устраивают
При подготовке тестовых данных, я исхожу из того, что результат должен быть проверяем.
Конечно. Для каждой записи в списке связей можно проверить, какого типа в ней муравей, и если он того типа, по которому гоняется тест, проверить, занесен ли он в нужный файл, и указана ли там правильная ячейка.
В существующей программе надо дописать процедуру подкачки (она не видится слишком сложной). И последовательно по миллиарду записей подкачивать данные в оба массива.
вам не кажется, что ваше решение масштабируется немножко нелинейно?
Мы вполне можем за первый проход составить карту файла — с какого места начинается первая запись, с какого N+1-я, и т.д. Тогда читать очередной миллиард будет легко.
Я имею в виду, что ваше решение хорошо работает до тех пор, пока идентификаторы числовые.
Идентификаторы могут иметь любую последовательность и любые пропуски, главное, чтобы размер массива был не меньше максимального числового значения идентификатора.
int totalMax = 1000000000;
antList = new byte[totalMax + 1];
cellList = new byte[totalMax + 1];
/*...*/
int idx = int.Parse(strList[0]);
antList[idx] = byte.Parse(strList[1]);
/*...*/
int idx = int.Parse(strList[0]);
cellList[idx] = byte.Parse(strList[1]);
Максимальное значение (результат инкрементальной функции) не вычисляется, а смотрится в БД и устанавливается с запасом
Ну, вы же понимаете, что у вас тогда расход памяти еще веселее становится?
И нет, это не типовые условия, потому что, например, у меня в типовых проектах целочисленных идентификаторов не было лет пять-восемь.
В память всё влезло и никто не парился.
А разве вы закладываете в проекты только универсальные решения?
Вот только этот «банальный вывод» противоречит тезису из «краткого резюме»
Влияет ли объём данных на трудоёмкость разработки. Учёт в муравейнике