Программная транзакционная память с большой общей информацией

Оригинальный вопрос

Я новичок в STM. Одна вещь, которую я хотел бы сделать в Haskell, включает в себя большую часть данных и множество легких потоков, которые читают и записывают на небольшие части упомянутой большой части данных. Места, считываемые и записываемые, могут считаться по существу случайными и малыми. STM кажется большим для этого, но у меня есть некоторые вопросы относительно того, как атаковать проблему. Я вижу несколько возможных подходов, каждый из которых имеет некоторые недостатки, а некоторые из них кажутся довольно глупыми. Некоторые комментарии к этим или другим идеям для альтернатив были бы высоко оценены.

Пусть для простоты предположим, что большая часть данных является Data.Vector a, где сами элементы сами по себе малы.

  • Весь фрагмент данных как один TVar (Vector a). Я предполагаю, что это заставит много копировать огромную часть данных, поскольку STM будет думать, что каждая отдельная запись, возможно, затронула все общие данные. Разумеется, нет никакой магии, когда STM идентифицирует, что чтения и записи очень локализованы, и что согласованность по большому фрагменту данных не требуется?

  • Огромное количество TVar a s, по существу одно для каждого элемента, дающее полностью локализованное STM, но по существу дублирование всего Vector a. Это кажется глупым.

  • Компромисс между 1 и 2 путем сегментации данных, чтобы у меня было разумное число TVar (Vector a), соответствующее подвекторам данных. Я считаю, что это решение слишком сильно зависит от эвристики, например, насколько важны сегменты.

  • Сообщения

    . Вместо того, чтобы каждый рабочий читал и записывал данные с помощью STM, каждый пишет сообщения с запросами на чтение данных или фрагменты данных, которые должны быть записаны через некоторый механизм STM, например, TChan. Специальный поток получает эти сообщения, передает данные, запрошенные через другой TChan, или принимает полученные данные и записывает их в общую структуру данных. Это решение кажется свободным от проблем, связанных с решениями 1-3, но мне также кажется, что он по существу отказывается от использования тонкостей STM для обеспечения согласованности данных. Скорее, это просто передача сообщений. Конечно, часть передачи сообщения реализована с помощью STM, но моя реальная проблема решена с помощью передачи сообщений. STM кажется таким замечательным, передача сообщений так... meh.

Я думаю об этом правильно? Есть ли у кого-нибудь какие-либо намеки или другие предложения?

Имейте в виду, что у меня нет опыта работы с STM, и я еще не пробовал вышеуказанные решения. Я выйду из своего кресла, но иногда может быть хорошо подумать об этом, прежде чем что-либо попробовать.

Добавление: Пятый подход приходит от Натана Хауэлла и использует TArray. Это похоже на то, что я хочу, но документация говорит:

В настоящее время он реализуется как Array ix (TVar e), но в будущем он может быть заменен более эффективной реализацией (однако интерфейс останется прежним).

Я полагаю, это означает, что TArray - это только мой подход номер 2 в более приятной одежде. Документы, намекающие на "более эффективную" реализацию, интересны, так как намеки на то, что на самом деле есть более хороший подход.

Последующий ответ на вопрос Вагифа Верди

Vagif Verdi answer очень интересен, поэтому я сделал небольшой небольшой эксперимент попробовать. У меня нет времени, чтобы сгладить код прямо сейчас, поэтому те, кто заинтересован в этом, должны будут нести ко мне код, который не просто содержит простые вещи. Я решил использовать изменяемый вектор с 10 ^ 8 Int как "большую общую часть данных", и пусть несколько читателей/писателей соответствуют потокам, принимаемым в сетевой сокет.

Обратите внимание, что код даже не читает или не записывает в общую часть данных. Он просто там, и каждый поток имеет в нем TVar.

Так что же происходит? Я запускаю программу, и сразу она занимает около 780 МБ ОЗУ, что и следовало ожидать (это примерно то, что нужно 10 ^ 8 Int). Теперь, если я использую netcat для подключения нескольких клиентов и напишу немного текста, который программа должна только распечатать и даже не записать в общие данные, процесс "использование ЦП достигает 100% в течение более секунды! Там заметное отставание перед текстом отображается. С яркой стороны использование памяти остается постоянным (в соответствии с ответом Вагифа Верди). Если я удалю вектор и TVar, т.е. Вытащу все STM и общие данные, все будет очень быстрым и отзывчивым, и нет заметного использования ЦП, когда клиент что-то пишет.

Итак, хотя очень приятно видеть, что общие данные на самом деле не дублируются (хотя я полагаю, что я должен написать общие данные, чтобы полностью проверить это), существует очень тяжелое снижение производительности, связанное с поддержанием согласованного государство. Для меня теперь остается вопрос: как правильно атаковать эту проблему, сохраняя тонкости STM?

Спасибо Вагифу Верди за то, что он поднял очень интересные моменты.

Ответ 1

STM не волшебство. Если у вас есть один гигантский TVar, он должен будет заблокировать все потоки, кроме одного, в любой момент времени. Это эквивалентно "крупнозернистой фиксации" и имеет то преимущество, что оно прост в использовании, но вся точка STM не должна быть принудительно использована для крупнозернистой фиксации. Эффективно только один поток может писать одновременно с этим подходом. То же самое происходит с вашей системой передачи сообщений - один поток является узким местом, ограничивающим масштабируемость.

Я не думаю, что существует большая проблема с использованием массива TVar, и я не знаю, почему вы описываете свой подход 2 как "глупый". Это именно то, что было изобретено STM.

Изменить: я призываю заинтересованные стороны смотреть это видео или, по крайней мере, его начало, для обсуждения некоторых мотивов STM. Это несколько лет, и материал по Transactional Boosting не очень уместен, но Herlihy блестящий и один из Computer Scientists, которому удается сделать интересную область, даже если это не ваша вещь.

Ответ 2

Прежде всего, Vector - неизменяемая структура данных. Каждый раз, когда вы "изменяете" Vector, вы создаете совершенно новую его копию, то есть каждая модификация принимает время O (n) (где n - длина вектора).

Во-вторых, значения в Haskell неизменяемы. Каждый раз, когда вы изменяете TVar, вы заменяете старое значение новым значением.

Я думаю, что вы хотите, это чисто функциональная структура данных, поддерживающая эффективное обновление. Два примера:

  • Data.Map: словарь ключевых слов. Это похоже на std::map в С++.

  • Data.Sequence: как измененный массив, но лучше.

Каждый раз, когда вы "модифицируете" одну из этих структур данных, вы фактически создаете новое значение, которое внутренне указывает на части старого значения.

Пара рекомендаций:

  • Если вы изменяете только одно значение, atomicModifyIORef может быть достаточно. Если требуется более сложная синхронизация, помимо атомного обновления, лучшим выбором будет STM.

  • Следите за лень при работе с изменяемыми переменными. Каждый раз, когда вы изменяете общее состояние, обязательно принудийте его. Это можно сделать, используя seq. Более удобным способом является использование шаблонов ударов. Пример:

    !x <- atomically $ do
        x <- readTVar shared_state
        writeTVar shared_state (changeSomething x)
        return x
    

    Это заставляет x оцениваться после завершения транзакции. Если переменная (IORef, STRef, TVar и т.д.) Модифицируется несколько раз, но никогда не принудительно, в памяти накапливаются thunks. Оценка полученного thunk может даже привести к переполнению стека.

Если ваша программа должна быть массово параллельной (т.е. одновременно иметь несколько ядер/процессоров одновременно с переменной), обновление одного значения, подобного этому, может быть менее эффективным, поскольку вычисления могут быть дублированы между процессорами. Однако для небольшого числа ядер дублирование довольно редко.

Ответ 3

Haskell имеет несколько реализаций изменяемых массивов/векторов. Таким образом, вы можете пойти с простейшим подходом TVar (Vector a) и не беспокоиться о копировании служебных данных (копирование в изменяемых массивах отсутствует)

Здесь одна такая библиотека: http://hackage.haskell.org/package/vector-0.9.1