Когда можно использовать параллельные массивы?

14

Я сталкивался с кодом (новым кодом), который использует то, что я называю 'Parallel Arrays' или Lists. Это означает, что есть 2 массива, которые содержат связанные данные и связаны их положением (индексом) в массиве.

Я считаю это запутанным и подверженным всевозможным ошибкам. Решение, которое я обычно предлагаю, состоит в создании объекта Companyс полями CompanyId и CompanyName.

Очень реальный пример:

List<string> companyNames;
List<int> companyIds;

//...They get populated somewhere and we then process

for(var i=0; i<companyNames.Count; i++)
{
    UpdateCompanyName(companyIds[i],companyNames[i]);
}

Эти параллельные массивы считаются плохой практикой ?

GER
источник
9
Просто еще одно доказательство того, что не было изобретено ни одного языка, на котором нельзя писать фортран.
Энди Манго
3
Кэширование может иметь (весьма существенные) преимущества при выполнении чего-то подобного (хотя вам нужны непрерывные массивы, а не связанные списки), и это стало несколько популярным в игровом программировании, связанном с «ориентированным на данные дизайном». Тем не менее, это не относится к вашему делу. Не похоже, что вы делаете критичный для производительности код.
Дерек Элкинс покинул SE
2
@DerekElkins ... Интересно, что ваш комментарий следует за тем, сравнивая это с кодом Фортрана. В ранних версиях Fortran отсутствовала поддержка пользовательских структур, и даже после добавления идиоматический код Fortran использует несколько массивов свойств, а не массивов структур. И это часто считается причиной того, что фортран часто считается самым быстрым языком.
Жюль
3
Мысль, касающаяся этого вопроса: многие функциональные языки активно поощряют работу с такими списками. У них есть функция, обычно называемая zip, которая преобразует их в список кортежей. Ваш код выглядит как C #. В последней версии C # добавлена ​​поддержка кортежей первого класса. Интересно, поэтому они где-то добавили функцию zip, которая автоматически помещала бы ваши списки в полезную для вас структуру?
Жюль
4
Ну, иногда есть причины для преднамеренного использования двух массивов, но в 99% всех случаев, которые я видел, единственной причиной этого была лень первоначального автора вводить охватывающую структуру данных.
Док Браун

Ответы:

23

Вот несколько причин, по которым кто-то может использовать массивы parrel:

  1. На языке, который не поддерживает классы или структуры
  2. Чтобы избежать блокировки потоков, когда отдельные потоки изменяют только один из столбцов
  3. Когда метод постоянства заставляет эти вещи храниться отдельно, а вы их восстанавливаете.
  4. Они могут потреблять меньше памяти, если структуры заполнены. (неприменимо для этих типов данных в C #)
  5. Когда части данных должны храниться близко друг к другу, чтобы эффективно использовать кэш ЦП (не поможет в приведенном выше коде).
  6. Использование кодов операций с одной инструкцией и несколькими данными (SIMD). (неприменимо для этого кода или строк)

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

TheCatWhisperer
источник
3
Они также могут потреблять меньше памяти, если структуры заполнены. Несколько больших массивов, выделенных разумно, могут потреблять меньше памяти, чем массив структур.
Фрэнк Хайлеман,
4
4. Когда части данных должны храниться близко друг к другу, чтобы эффективно использовать кэш ЦП. (Необходим в редких случаях.)
Blrfl
@Frank Hileman, хотя я думаю, что ответ TheCatWhisperer является полностью правильным, ваш комментарий, на самом деле, является лучшей причиной для выбора этого подхода. Если потребление памяти критично, накладные расходы памяти при заполнении структур могут быть значительными, особенно если в игре используются большие числа.
Владимир Стокич
Добавил свои предложения в ответ
TheCatWhisperer
Re (2), как это? Я могу написать программу с одним массивом структур и блокировкой на поле так же легко, как написать программу с несколькими массивами и блокировкой на массив.
Соломон Медленный
7

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

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

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

candied_orange
источник
6

Этот шаблон иногда также называют структурой массивов (в отличие от массива структур) и чрезвычайно полезен при векторизации кода. Вместо того, чтобы писать вычисления, которые выполняются на одной структуре, и векторизовать их биты, вы пишете вычисления, как обычно, за исключением встроенных функций SSE, чтобы они выполнялись на 4 структурах вместо одной. Обычно это проще и почти всегда быстрее. Формат SoA делает это очень естественным. Это также улучшает выравнивание, что ускоряет работу памяти SSE.

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