Когда я могу вызвать функцию, используя изменяемые переменные параллельно? - PullRequest
0 голосов
/ 03 июня 2018

После просмотра интересной лекции Phil Trelford

https://www.youtube.com/watch?v=hx2vOwbB-X0

я был заинтригован возможностью ускорения моего кода путем замены списков массивами и, в более общем смысле, использования изменяемых переменных,Поэтому я сделал простой тест:

let xs = [0.0..1000000.00]
let axs = List.toArray xs

let f x = sin (x * x)

List.map f xs // Real: 00:00:00.170, CPU: 00:00:00.187, GC gen0: 5, gen1: 3, gen2: 1

Array.map f axs // Real: 00:00:00.046, CPU: 00:00:00.046, GC gen0: 0, gen1: 0, gen2: 0

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

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

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

В общем, когда можно использовать изменяемые переменные без риска возникновения проблем с распараллеленным кодом?Существует ли простой тест, который позволил бы мне определить надежность функции при параллельном вызове?

Ответы [ 2 ]

0 голосов
/ 04 июня 2018

Хотя @rmunn предоставил отличный ответ на настоящий вопрос, я чувствую, что должен написать это дополнение, потому что считаю его очень важным и слишком длинным, чтобы помещаться в комментарии.

Этоозначает ответить на подразумеваемый вопрос, который я прочитал как " Поскольку изменяемые данные работают быстрее, разве я не должен всегда использовать изменяемые данные? "

Это действительно такчто, как правило, изменяемые структуры данных, если вы понимаете их правильно, быстрее на поверхности, так почему бы нам не использовать их постоянно?Также верно, что прыжки, если вы понимаете это правильно, быстрее, чем вызов функции, так почему же мы не используем goto все время ?Верно также и то, что ручное управление памятью, если вы понимаете это правильно, использует меньше памяти, и быстрее, чем сборка мусора, так почему мы используем сборку мусора?И это (возможно) верно, что написание непосредственно в ассемблере или даже в двоичном коде, если вы понимаете, что действительно, действительно правильно, быстрее, чем компиляция, так почему же у нас вообще есть языки высокого уровня?

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

При проектировании вашей системы сначала попытайтесь угадать , где могут быть узкие места, а затем спроектируйте эти места.осторожно и положите вокруг них немного каротажа и инструментов.При кодировании программ сначала делайте их читабельными, понятными, поддерживаемыми.Затем измерьте производительность - на производстве или в промежуточной среде, если вы можете себе это позволить.И под «мерой» я не имею в виду «это самый быстрый способ, которым это может быть?», Я имею в виду «это достаточно быстро для наших целей?».Если это так, хорошо.Если это не так, выясните, где именно происходит замедление, и оптимизируйте это место.Со временем, с опытом, ваши догадки для потенциальных узких мест будут становиться все лучше и лучше.

Не пытайтесь оптимизировать заранее: вы просто получите беспорядок в ваших рукахи тебе придется скоро его выбросить. Преждевременная оптимизация - корень всего зла .

0 голосов
/ 04 июня 2018

Разница в скорости с массивами не имеет ничего общего с изменчивостью;все дело в местонахождении кэша .Массивы являются непрерывными в памяти, поэтому их можно выполнять быстрее, чем списки: списки F # являются односвязными списками, поэтому каждый элемент может быть (и обычно находится) в другом месте памяти.Это означает, что вы не извлекаете выгоду из кэша ЦП, тогда как с массивами, как только вы заплатили стоимость извлечения первого элемента из памяти, затем элементов со 2-го по N-й (где значение N зависит от размераэлементы, которые вы извлекаете) уже находятся в кэше и готовы к почти мгновенному поиску.Если бы F # имел класс ImmutableArray, и вы использовали его, вы получите те же преимущества в скорости при отображении этого ImmutableArray, что и из вашего изменяемого массива.

Что касается вашего основного вопроса, о том, когда безопасно использовать изменяемые переменныепри параллельном коде простой тест состоит в том, чтобы спросить: «Действительно ли я изменяю данные, которые используются несколькими потоками?»Если вы не изменяете свои данные, тогда безопасно, чтобы несколько потоков обращались к ним параллельно.Даже если данные могут быть видоизменены (например, массив), если вы на самом деле не изменяете их, тогда ваш параллельный код не столкнется с проблемами.Если вы действительно изменяете данные, вам приходится иметь дело с блокировкой и всеми проблемами, которые возникают вместе с блокировкой, такими как нехватка ресурсов, взаимоблокировки и т. Д.

Таким образом, простое правило:«Мутирующие данные + Параллелизм = Боль».Если вы изменяете свои данные, но не запускаете параллельный код, вам будет гораздо меньше боли.Если вы не изменяете свои данные, то параллельный код не причинит вам боль.Но если вы делаете оба, будьте готовы к головным болям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...