В общем, единственный способ ответить на такого рода вопросы производительности это профиль их в вашем реальном коде. Microbenchmarks часто вводят в заблуждение (см., Например, this benchmarking tale) - и особенно, если вы говорите о параллелизме, лучшая стратегия может быть очень различной в зависимости от того, насколько на практике используется ваш случай использования.
Теоретически, достаточно интеллектуальный компилятор ™ должен быть способен - возможно, с помощью системы линейного типа (предполагаемой или иной) - воспроизводить все преимущества эффективности изменяемой структуры данных. Фактически, поскольку у него есть больше информации о намерениях программиста и он менее ограничен случайными деталями, которые должен был указать программист, такой компилятор должен иметь возможность генерировать более высокопроизводительный код - и, например, GCC переписывает код в неизменяемую форму (SSA) для целей оптимизации. Для примера, который приближается к дому, многие Java-программы реального мира обладают достаточной пропускной способностью, но имеют проблемы с задержкой, вызванные сборщиком мусора Java, который останавливает мир, чтобы сжать кучу. JVM, который знал, что некоторые объекты неизменяемы, сможет перемещать их, не останавливая мир (вы можете просто скопировать объект, обновить все ссылки на него и затем удалить старую копию, поскольку не имеет значения, если некоторые потоки см. старую версию, а некоторые из них видят новую).
На практике это зависит, и снова единственный способ - проверить свой конкретный случай. По моему опыту, для уровня инвестиций времени программиста, доступного для большинства практических бизнес-задач, расходы x часов на (неизменяемую) версию Scala, как правило, дают более эффективную программу, чем расходы того же времени на изменяемую версию Scala или Java - действительно, в размере времени программиста, которое требуется для создания приемлемо исполняющейся версии Scala, вероятно, вообще невозможно завершить версию Java (особенно, если нам требуется такая же скорость дефекта).С другой стороны, если у вас есть неограниченное время для опытного программиста и вам нужно получить максимально возможную производительность, вы, вероятно, захотите использовать очень низкоуровневый изменяемый язык (вот почему LAPACK все еще написан на Fortran) - или даже реализовать свой алгоритм непосредственно на FPGA, как недавно сделал JP Morgan.
Но даже в этом случае вы, вероятно, захотите иметь прототип на языке более высокого уровня, чтобы вы могли писать тесты и сравнивать их, чтобы убедиться, что высокопроизводительная реализация работает правильно. В частности, если мы просто говорим о изменчивом и неизменяемом в Scala, преждевременная оптимизация - это корень всего зла. Напишите свою программу, а затем, если производительность неадекватна, просмотрите ее и посмотрите на горячие точки. Если вы действительно тратите слишком много времени на копирование неизменяемой структуры данных, это подходящее время для замены его изменчивой версией и тщательно проверяйте гарантии безопасности потоков вручную. Если вы пишете правильно развязанный код, тогда вам должно быть легко заменить критичные по производительности элементы как и когда вам нужно, и до тех пор вы сможете пожинать время разработки кода, которое проще и проще рассуждать (особенно в параллелизме случаев). По моему опыту проблемы производительности в хорошо написанном коде намного реже, чем люди ожидают; большинство проблем с производительностью программного обеспечения вызваны плохим выбором алгоритма или структуры данных, а не такими небольшими накладными расходами.
Структурный обмен означает, что совершенно новая копия часто не требуется. –
«Если каждая операция мутации в неизменяемой структуре данных возвращает новый экземпляр», это не дублирует весь экземпляр, неизменяемые структуры позволяют совместное использование данных. – vptheron
У вас есть конкретный пример, в котором недостаточно производительности для понимания? –