Как стать автором
Обновить

Комментарии 45

>> Не исключено, что придется и дальше повторно использовать память везде, где возможно, и не злоупотреблять динамической памятью. Но может быть, и нет.

Можно, я перефразирую это как «я не смог сделать никаких адекватных и полезных выводов из намерянных цифр», а? Спасибо

P.S. А вы вообще в курсе, как GC в CLR, работает? Ну, там про сегменты ОС в 32МБ и VirtualAlloc, Large Object Heap знаете?
Да, теорию я читал. Вот только без цифр она оставалась пустым звуком.
интересно, а создание одномерного массива, самодельного списка объекта типа List тоже распаралелливали?
Нет, каждый thread работал со своей версией списка. Так же, как и со своей версией массивов. Так что в режиме 4 threadов памяти было занято в 4 раза больше.
так а что тогда получается: я правильно понял что вы создаете, например, 4ре потока — каждый поток работает со своим экземпляром объекта, и каждый независимо (со своего экземпляра) читает/записывает данные?
Да. И независимо захватывает память, и независимо дерется за свободное место. А сборщик мусора отдувается за всех.
Эта модель больше похожа на мою реальную ситуацию.
так а с чего вы решили что такое распаралелливание должно работать быстрее....? за счет чего происходит увеличение скорости в ваших тестах? Вы считаете время для каждого потока а потом среднее? Или ждете пока все потоки закончат все действия и считаете время от старта до этого момента?
Считаю время от старта до завершения последнего потока. В некоторых случаях получается эффективность 80% (короткий одномерный массив, только чтение/запись — там потокам не нужно драться даже из-за доступа к памяти), в других — скорость даже понижается (самодельные списки, длина 400000: когда объектов стало вчетверо дольше, скорость работы с ними уменьшилась больше, чем в 4 раза). А увеличение скорости (если оно есть) происходит за счет того, что у некоторых операций все-таки появляется шанс работать параллельно.
что значит эффективность? Вы называете эффективность=(время в одном потоке / время в p потоках )/ p? (где p — количество потоков). Я просто пытаюсь понять — у вас получается что если возьмем короткий одномерный массив и будем читать/записывать с него в одном потоке, то это будет медленнее чем если возьмем несколько потоков, каждый из которых будет иметь свой независимый короткий одномерный массив и КАЖДЫЙ поток будет читать со СВОЕГО массива, при этом меряя время от старта первого потока до окончания последнего?
эффективность=(«время на обработку 1 элемента для случая одного потока» / «время на обработку 1 элемента для случая потоков» )/ p = «время на обработку одного массива одним потоком»/«время на обработку p массивов p потоками».

Для случая массива int[20000]: один поток тратит на заполнение+чтение T1=20000*1.56нс = 31.2 мкс. Четыре потока, работая с 4 независимыми массивами, справятся за T4=4*20000*0.47нс=37.8 мкс. Как видно, T1<T4, так что один поток справится быстрее. Эффективность = 31.2/37.8=82%. Если ее посчитать, как 1.56/0.46/4, получится столько же.
с чего вы взяли что что «нс» разные в случае нескольких потоков и в случае одного потока? Как вы вообще считаете эти «нс»? Очевидно, что если каждый поток ну вообще никак не зависит от любого другого то почему время разное? Или все таки зависят эти 4ре потока друг от друга как-то?
Да, зависят. Первый поток запускает всех остальных, а потом, после обработки своего массива, ждет их завершения. Он же и измеряет время. По системным часам. Мне важно не то, сколько тактов было отдано тому или иному процессу, а то, сколько наносекунд пользователь ждал решения задачи. А «нс» везде одинаковы, и равны 9-с-чем-то периодам какого-то там излучения.
То, что время разное — экспериментальный факт. Программа запускалась два раза (для каждой клетки таблицы). Один раз работал только один поток, а другой — все четыре.
я не спрашивал что такое «нс», я спрашивал как вы их считаете) что то вы тут путаете, то потоки то процессы… вообще не ясно «Первый поток запускает всех остальных, а потом, после обработки своего массива, ждет их завершения.» каким образом операции создания-записи-чтения в ОДНОМ потоке соизмеримы с операцией запуска создания-записи-чтения того же массива в одном потоке который плюс к тому же запускает еще 4ре таких же и ждет их завершения? т.е. вы хотите сказать что если я буду выполнять операцию скажем создания-записи-чтения массива длиной 200000 в ОДНОМ потоке, а потом запущу поток который сделает тоже самое и после этого запустит еще 4ре потока которые будут делать опять же то же самое (создание-запись-чтение массива длинной 200000), то время выполнения будет быстрее во втором случае? Если так то это что то не то вы меряете)
Полное время выполнения будет, конечно, больше. Но в таблице у меня указано время на один обработанный элемент массивов/списков. А обработанных элементов во втором случае в 4 раза больше.
И, кстати, thread запускает три других thread'a не после, а до того, как он начинает работать со своими массивами. А после того, как он закончит обработку, он ждет завершения остальных.
Что здесь не так?
все, теперь ясно. не дочитался что запускаются thread'ы до работы с массивом.
На самом деле, это неважно. Если три новых thread'а запускаются после окончания обработки первого массива, на обработку их массивов потребуется время T3 и если T3<3*T1, то среднее время на 1 обработанный массив будет (T1+T3)/4<T1. В случае самодельных списков может получиться даже T1+T3<T4: объектов в памяти одновременно находится меньше, чем при одновременной обработке четырех списков.
а ничего, что в такой задаче узким местом должен быть винт?
Ничего — файл обычно уже прочитан и лежит где-то в системном кэше. Простое чтение (без обработки) 600-Мб файла у меня занимало меньше секунды.
(конечно, если читать его второй раз. В первый и 15 секунд может набежать).
Да, проверил еще раз. Файл 1.5 ГБ. Первое чтение — 5.73 сек, второе — 1.07 сек. Узким местом не будет. Особенно если читать асинхронно.
Маппинг файла в память в разы быстрее считывания
Может быть. А в стандартной .NET библиотеке он есть? И можно ли отобразить только фрагмент файла (не с начала)? А то программу приходится запускать с 32-битном режиме — она использует Managed DirectX.
Увидел, что можно. Надо будет попробовать. Не люблю виндовых вызовов…
Вот мсдн: Memory-Mapped Files.
Тут нет нигде неприкрытого WinAPI,
так что под Mono тоже должно работать.
Спасибо
Причем тут Managed DirectX?
А у него есть 64-битная версия?
Это смотря что с файлом делать. Если его не читать, то да. А если его всё равно придётся прочитать полностью, то может быть быстрее его полностью в память запихать.
Полностью может не влезть :(
В любом случа маппинг быстрее и удобнее. Не надо заботиться о подкачке. Просто берем и читаем из любого места файла. Даже если чтение только подряд — всё равно удобнее просто работать с памятью, чем считывать куски файла в буфер.
Как раз если чтение подряд, то всё равно. А если бегать по файлу туда-сюда, то может возникнуть разница как между последовательным и случайным доступом. Я это наблюдал на примере майкрософтовского линкера и лечил предварительным кешированием файлов, ускорение вышло в разы.
Надо попробовать проект создать на RAM-диске. Только нужно скрипит ком раз в пару минут сэйвить входные файлы проекта наружу, как бы их не потерять.
Входные файлы как раз сохранять не надо, они проектом не меняются. Вот выходные надо сохранять аккуратно. Но Винды и так все за нас делают: фактически файл читается уже из памяти (если он уже был просмотрен хотя бы один раз).
Решать наверное можно по-разному, я просто привёл пример когда «быстрее и удобнее» мэппингом приводит к проблемам. В моём случае запихнуть в кэш оказалось достаточно, что можно сказать эквивалентно рамдиску.
> Кроме того, видно, что моя реализация списка системе не понравилась совсем

Полагаю, дело в том, что у вас класс list — связный список, использующий указатель на следующий элемент, а встроенный List — это динамический массив.
Это понятно, я просто экспериментировал с маленьким объектом ссылочного типа. В реальных программах на С# я такое не использую. Но то, что программа при переходе от 40000 к 4 млн объектов замедлилась в 7 раз (в пересчете на объект), было несколько неожиданно. Они же все одинакового размера!
Могу сделать предположение, что это кэш. Если есть желание — попробуйте построить график скорости от количества объектов (с достаточно большим количеством точек) только для вашего класса. График должен будет показать границу достаточно резкого падения производительности.
Построю. И скорость прохода по списку посчитаю поточнее. Но думаю, что кэш проявит себя гораздо раньше, уже на 40000.
График зависимости времени создания/освобождения списка от числа его элементов выглядит так:
image
По вертикали — время в наносекундах на 1 элемент списка. Масштаб по обеим осям логарифмический.
Как такое можно предсказать, исходя из знаний о SOH/LOH и Virtual Alloc — ума не приложу.
Я конечно извиняюсь, сам не пересчитывал ваши расчеты, но точно 0.3нс/слово? нс — наносекунды? 10 в минус 9 степени секунды? Время за которое свет проходит чуть меньше 30 сантиметров?
Да, правильно. 0.3/нс = 0.075 секунды/гигабайт.
(в смысле 0.3 нс/слово=0.075 секунды/гигабайт)
При подобном чтении файла, по любому отображаемые в память файлы будут быстрее всего, можно использовать unsafe вставки для прямого доступа по адресам.
А вот массив у вас не многомерный, а вложенный. Многомерные массивы описываются как «T[,]», а не «T[][]».
Кроме того в BCL есть встроенный класс System.Collections.Generic.LinkedList<T>
Массив не прямоугольный, а зубчатый. Это верно. Но разве он перестал от этого быть многомерным?
Насчет «быстрее» я еще буду проверять. Немного смущает, что перед функциями написано ".Net 4": решения уводить проект с 3.5 еще не было принято, а пользователи — народ консервативный.
Впрочем, у меня работает упреждающее асинхронное чтение файла, и можно считать, что на него вообще не тратится времени: к тому моменту, когда мне требуется следующая секция, она уже оказывается прочитанной. А если нет, то и другие методы чтения не успели бы. Против медленного диска не устоишь.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации