Параллельные вычисления

Обработка данных: параллелизм и производительность

Джонсон М. Харт

Загрузка примера кода

Обработка наборов данных — фундаментальная вычислительная задача, которой, как и ряду других практических задач, изначально свойствен параллелизм, потенциально способный повысить производительность и пропускную способность в многоядерных системах. Я сравню несколько подходов в Windows к решению задач с высокой степенью параллелизма данных.

Эталонный тест, которым я воспользуюсь в этом сравнении, — задача поиска («Geonames») из главы 9 книги Трой Магеннис (Troy Magennis) «LINQ to Objects Using C# 4.0» (Addison-Wesley, 2010). Альтернативные решения таковы:

  • PLINQ (Parallel Language Integrated Query) и C# 4.0 — с расширениями оригинального кода и без;
  • неуправляемый код для Windows с использованием C, Windows API, потоков и проецируемых в память файлов (memory-mapped files);
  • многопоточный код для Windows C#/Microsoft .NET Framework.

Исходный код для всех решений доступен на моем веб-сайте (jmhartsoftware.com). Другие методики параллелизма, например Windows Task Parallel Library (TPL), напрямую не исследовались, хотя PLINQ является уровнем, размещаемым поверх TPL.

Сравнение и оценка альтернативных решений

Критерии оценки решения (в порядке убывания значимости) следующие:

  • общая производительность, выраженная как время, затраченное на выполнение задачи;
  • масштабируемость в соответствии со степенью параллелизма (количество задач), числом ядер и размером набора данных;
  • простота и элегантность кода, удобство в его сопровождении и прочие не столь важные факторы.

Краткие итоги

Эта статья продемонстрирует, что для репрезентативной задачи поиска эталонных данных:

  • можно успешно эксплуатировать многоядерные 64-разрядные системы для повышения производительности во многих задачах, связанных с обработкой данных, и PLINQ может быть частью этого решения;
  • эффективное и масштабируемое применение PLINQ требует использования объектов с индексируемыми наборами данных — одной поддержки интерфейса IEnumerable недостаточно;
  • решения на основе C#/.NET и неуправляемого кода самые быстрые;
  • исходное решение на основе PLINQ медленнее примерно в 10 раз, и оно не масштабируется более чем до двух задач, тогда как другие решения отлично масштабируются до шести задач при наличии шести ядер (максимальное протестированное количество ядер). Однако усовершенствования в коде значительно увеличивают эффективность исходного решения;
  • код на основе PLINQ самый простой и элегантный во всех отношениях, так как LINQ поддерживает декларативные запросы к находящимся в памяти и внешним данным. Неуправляемый код получается громоздким, а код C#/.NET значительно лучше (но не так прост), чем код на основе PLINQ;
  • все методы хорошо масштабируются с увеличением размеров файлов до предельного объема физической памяти в тестируемой системе.

Эталонный тест: Geonames

Идея этой статьи навеяна девятой главой книги Магеннис, посвященной LINQ, где применение PLINQ демонстрируется на примере поиска по географической базе данных, содержащей более 7,25 миллионов географических названий (топонимов) (place name) в файле размером 825 Мб (более чем один топоним на каждую тысячу человек). Каждый топоним представлен записью с текстовой строкой варьируемой длины в кодировке UTF-8 (en.wikipedia.org/wiki/UTF-8), разделенной более чем на 15 полей с помощью знаков табуляции. Примечание: кодировка UTF-8 гарантирует, что символ табуляции (0x9) или подачи строки (0xA) не будет частью байтовой последовательности; это важно для нескольких реализаций.

Geonames — программа Магеннис — содержит «зашитый» в код запрос для идентификации всех географических названий с высотой над уровнем моря (поле 15) более 8000 метров; при этом она показывает географическое название, страну и высоту над уровнем моря, а также выполняет сортировку в порядке убывания высоты. Если вас это интересует, то таких мест всего 16, и самое высокое — гора Эверест, пик которой возвышается на 8848 метров.

Магеннис сообщает о 22,3 секунды (одно ядро) и 14,1 секунды (два ядра). Предыдущий опыт (для примера, см. мою статью «Windows Parallelism, Fast File Searching and Speculative Processing» по ссылке informit.com/articles/article.aspx?p=1606242) показывает, что файлы такого размера можно обрабатывать за несколько секунд, а производительность отлично масштабируется с ростом числа ядер. Поэтому я решил попытаться применить тот опыт и попробовать усовершенствовать PLINQ-код Магеннис для большей производительности. Первые же усовершенствования в PLINQ-коде почти удвоили производительность, но ничего не дали в плане масштабируемости; однако дальнейшие усовершенствования позволили добиться производительности практически на уровне многопоточного неуправляемого и C#-кода.

Этот эталонный тест интересен по нескольким причинам:

  • субъект (географические места и атрибуты) представляет практический интерес, и запрос легко делается универсальным;
  • высокая степень параллелизма данных; в принципе, каждую запись можно обрабатывать параллельно;
  • размер файла весьма скромен по нынешним меркам, но очень легко перейти на тестирование более крупных файлов простой многократной конкатенацией файла allCountries.txt (из Geonames) самого с самим;
  • обработка требует поддержки состояний; это необходимо для определения границ строк и полей, чтобы разбивать файл, а строки нужно обрабатывать, чтобы находить индивидуальные поля.

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

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

Сравнение производительности

Первая тестовая система — шестиядерный настольный компьютер под управлением Windows 7 (AMD Phenom II, 2,80 ГГц, 4 Гб RAM). Позднее я представлю результаты для трех других систем с поддержкой «гиперпоточности» (hyper-threading, HT) (en.wikipedia.org/wiki/Hyper-threading) и разным количеством ядер.

На рис. 1 показаны результаты для шести решений Geonames, где истекшее время (в секундах) является функцией «степени распараллеливания» («Degree of Parallelization», DoP) (т. е. количества параллельных задач, которое может превышать число ядер процессора); в тестовой системе было шесть ядер, но в реализациях контролировался DoP. Шесть задач — оптимальное число; использование более шести задач приводило к деградации производительности. Во всех тестах использовался оригинальный Geonames-файл данных allCountries.txt размером 825 Мб.

image: Geonames Performance as a Function of Degree of Parallelism

Рис. 1. Производительность Geonames как функция степени параллелизма

Использовались следующие реализации (более полные пояснения я дам позже).

  1. Geonames Original. Исходное решение Магеннис на основе PLINQ. Его производительность далека от желаемой, и оно плохо масштабируется с увеличением числа ядер.
  2. Geonames Helper. Улучшенная по производительности версия Geonames Original.
  3. Geonames MMChar. Неудачная попытка улучшения Geonames Helper с помощью класса проецируемого в память файла, аналогичного используемому в Geonames Threads. Примечание: проецирование в память позволяет ссылаться на файл так, будто он находится в памяти, без явных операций ввода-вывода, и это может дать выигрыш в производительности.
  4. Geonames MMByte. Это решение модифицирует MMChar для обработки индивидуальных байтов входного файла, тогда как предыдущие три решения преобразуют символы UTF-8 в Unicode (по два байта на каждый символ). Производительность наилучшая из первых четырех решений и вдвое превышает таковую для Geonames Original.
  5. Geonames Threads не использует PLINQ. Это реализация на основе C#/.NET с применением потоков и проецируемого в память файла. Производительность больше, чем для Index (см. ниже) и примерно одинакова с Native. Это решение и Geonames Native обеспечивают максимальную масштабируемость параллелизма.
  6. Geonames Index. PLINQ-решение с предварительной обработкой файла данных (отнимает примерно девять секунд) для создания размещаемого в памяти объекта List<byte[]>, с помощью которого осуществляется дальнейшая PLINQ-обработка. Издержки предварительной обработки можно амортизировать при использовании последующих запросов и выйти на производительность, лишь чуть-чуть уступающую быстродействию Geonames Native и Geonames Threads.
  7. Geonames Native (не показана на рис. 1) не использует PLINQ. Это реализация на основе C и Windows API с использованием потоков и проецируемого в память файла, описанная в главе 10 моей книги «Windows System Programming» (Addison-Wesley, 2010). Для достижения высоких результатов важно задействовать все оптимизации компилятора; оптимизация по умолчанию дает не более половины возможной производительности.

Все реализации являются 64-разрядными сборками. 32-разрядные сборки в большинстве случаев тоже работают, но не годятся для обработки больших файлов (см. рис. 2). На рис. 2 показана производительность с использованием DoP 4 и файлов большего размера.

image: Geonames Performance as a Function of File Size

Рис. 2. Производительность Geonames как функция размера файла

В данном случае в тестовой системе было четыре ядра (AMD Phenom Quad-Core, 2,40 ГГц, 8 Гб RAM). Файлы большего размера создавались конкатенацией нескольких копий исходного файла. На рис. 2 показаны лишь три самых быстрых решения, включая Geonames Index — самое быстродействующее решение на основе PLINQ (если не считать предварительной обработки файла); производительность масштабируется с увеличением размера файла, пока не достигнет пределов физической памяти.

Теперь я опишу последние шесть реализаций и подробнее рассмотрю методики на основе PLINQ. После этого мы обсудим результаты в других тестовых системах и подведем итоги.

Улучшенные решения на основе PLINQ: Geonames Helper

На рис. 3 показана реализация Geonames Helper с моими изменениями (выделены полужирным) в коде Geonames Original.

Рис. 3. Geonames Helper с выделенными изменениями в оригинальном коде на основе PLINQ

class Program
{
  static void Main(string[] args)
  {
    const int nameColumn = 1;
    const int countryColumn = 8;
    const int elevationColumn = 15;

    String inFile = "Data/AllCountries.txt";
    if (args.Length >= 1) inFile = args[0];
        
    int degreeOfParallelism = 1;
    if (args.Length >= 2) degreeOfParallelism = int.Parse(args[1]);
    Console.WriteLine("Geographical data file: {0}. 
      Degree of Parallelism: {1}.", inFile, degreeOfParallelism);

    var lines = File.ReadLines(Path.Combine(
      Environment.CurrentDirectory, inFile));

    var q = from line in 
      lines.AsParallel().WithDegreeOfParallelism(degreeOfParallelism)
        let elevation = 
          Helper.ExtractIntegerField(line, elevationColumn)
        where elevation > 8000 // elevation in meters
        orderby elevation descending
        select new
        {
          elevation = elevation,
          thisLine = line
         };

    foreach (var x in q)
    {
      if (x != null)
      {
        String[] fields = x.thisLine.Split(new char[] { '\t' });
        Console.WriteLine("{0} ({1}m) - located in {2}",
          fields[nameColumn], fields[elevationColumn], 
          fields[countryColumn]);
      }
    }
  }
}

Поскольку многие читатели могут быть не знакомы с PLINQ и C# 4.0, я дам несколько комментариев по листингу на рис. 3 и, в том числе, опишу усовершенствования.

  • Строки 9–14 позволяют в командной строке указывать имя входного файла и степень параллелизма (максимальное количество одновременно выполняемых задач); в оригинале эти значения «зашиты» в код.
  • Строки 16–17 начинают асинхронное чтение строк в файле и неявно типизируют их как строковый массив в C#. Значения из строк не используются до строк кода 19–27. В других решениях, например Geonames MMByte, применяется другой класс со своим методом ReadLines, и эти строки кода — единственное, что нужно изменить.
  • Строки 19–27 — это LINQ-код с PLINQ-расширением AsParallel. Этот код аналогичен SQL, и переменная q неявно типизируется как массив объектов, содержащих целочисленные высоты и строки. Заметьте, что PLINQ выполняет всю работу. связанную с управлением потоками; метод AsParallel — это все, что требуется для преобразования последовательного LINQ-кода в PLINQ-код.
  • Строка 20. На рис. 4 показан метод Helper.ExtractIntegerField. Оригинальная программа использует метод String.Split в том же стиле, что и при отображении результатов в строке 33 (рис. 3). Это ключ к более высокой производительности Geonames Helper по сравнению с Geonames Original, так как больше нет нужды создавать объекты String для каждого поля в каждой строке.

Рис. 4. Метод ExtractIntegerField класса Helper

class Helper
{
  public static int ExtractIntegerField(String line, int fieldNumber)
  {
    int value = 0, iField = 0;
    byte digit;

    // Skip to the specified field number and extract the decimal value.
    foreach (char ch in line)
    {
      if (ch == '\t') { iField++; if (iField > fieldNumber) break; }
      else
      {
        if (iField == fieldNumber)
        {
          digit = (byte)(ch - 0x30);  // 0x30 is the character '0'
          if (digit >= 0 && digit <= 9) 
            { value = 10 * value + digit; }
          else // Character not in [0-9]. Reset the value and quit.
          { value = 0; break; }
        }
      }
    }
    return value;
  }
}

Заметьте, что метод AsParallel в строке 19 можно использовать с любым объектом IEnumerable. Как я упоминал, на рис. 4 показан метод ExtractIntegerField класса Helper. Он просто извлекает и оценивает заданные поля (высоту над уровнем моря, в данном случае), избегая вызова библиотечных методов для большей производительности. Как вы видели на рис. 1, это усовершенствование удваивает производительность в случае DoP 1.

Geonames MMChar и Geonames MMByte

Geonames MMChar — это неудачная попытка повысить производительность проецированием в память входного файла с применением собственного класса FileMmChar. Однако Geonames MMByte все же дает значительный выигрыш, поскольку байты входного файла не преобразуются в Unicode.

MMChar требует создания нового класса, FileMmChar, который поддерживает интерфейс IEnumerable<String>. Класс FileMmByte аналогичен и имеет дело с объектами byte[], а не String. Единственное значимое изменение кода на рис. 3 заключено в строках 16–17, которые теперь выглядят так:

var lines = FileMmByte.ReadLines(Path.Combine(
    Environment.CurrentDirectory, inFile));

Код для:

public static IEnumerable<byte[]> ReadLines(String path)

который поддерживает интерфейс IEnumerable<byte[]> в FileMmByte, конструирует объект FileMmByte, а объект IEnumerator<byte[]> сканирует индивидуальные строки в проецируемом файле.

Заметьте, что классы FileMmChar и FileMmByte — «небезопасны», поскольку они создают и используют указатели для доступа к файлам, а также применяют взаимодействие между кодом на C# и неуправляемым кодом. Однако все операции с указателями изолированы в отдельной сборке, и код использует массивы вместо разыменования указателей (pointer dereferencing). Класс MemoryMappedFile в .NET Framework 4 тут ничем не поможет, так как нужно использовать функции-аксессоры для перемещения данных из проецируемой памяти.

Geonames Native

Geonames Native использует Windows API, потоки и проецирование файла в память. Базовые шаблоны кода описаны в «Windows System Programming» (глава 10). Программа должна управлять потоками напрямую и очень аккуратно проецировать файл в память. Ее производительность гораздо выше, чем у всех реализаций на основе PLINQ, кроме Geonames Index.

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

  • разбейте входной файл на четыре равных раздела, передавая каждому потоку начальное место в каждом разделе как часть аргумента функции потока;
  • пусть каждый поток затем обрабатывает все строки (записи), которые начинаются в разделе. Это означает, что поток скорее всего будет обращаться к следующему разделу для завершения обработки последней строки, с которой начинается текущий раздел.

Geonames Threads

Geonames Threads использует ту же логику, что и Geonames Native; по сути, часть кода одинакова или почти одинакова. Однако лямбда-выражения, методы расширения, контейнеры и другие средства C#/.NET значительно упрощают кодирование.

Как и в случае MMByte или MMChar, проецирование файла в память требует применения «небезопасных» классов и взаимодействия C#-кода с неуправляемым кодом для операций с указателями на проецируемую память. Но эти усилия того стоят, так как при гораздо более простом коде производительность у Geonames Threads такая же, как у Geonames Native.

Geonames Index

Результаты на основе PLINQ (Original, Helper, MMChar и MMByte) разочаровывают по сравнению с результатами Native и .NET Threads. Есть ли какой-нибудь способ задействовать простоту и элегантность PLINQ, не жертвуя производительностью?

Хотя определить точно, как именно PLINQ обрабатывает запрос (строки 16–27 на рис. 3) невозможно, вполне вероятно, что в PLINQ вообще нет хорошего способа разбиения входных строк для параллельной обработки раздельными задачами. В качестве рабочей гипотезы допустим, что разделение может быть причиной низкой производительности PLINQ.

Из книги Магеннис (сс. 276–279) следует, что String-массив строк в файле поддерживает интерфейс IEnumerable<String> (см. также книгу Джона Шарпа (John Sharp) «Microsoft Visual C# 2010 Step by Step» [Microsoft Press, 2010], главу 19). Однако строки не индексируются, поэтому PLINQ, по-видимому, использует «разделение на порции» («chunk partitioning»). Более того, методы IEnumerator.MoveNext классов FileMmChar и FileMmByte работают медленно из-за того, что им нужно сканировать каждый символ, пока не будет найдена следующая новая строка.

Что произошло бы, если бы все строки в String-массиве были проиндексированы? Увеличилась бы производительность PLINQ, особенно в сочетании с проецированием входного файла в память? Geonames Index показывает, что эта методика действительно повышает производительность, давая результаты, сравнимые с неуправляемым кодом. Но так или иначе, в целом, неизбежны издержки, связанные с предварительной подготовкой, т. е. перемещение строк в список или массив в памяти и его последующая индексация (эти издержки можно амортизировать при нескольких запросах).

Предварительная индексация файла несложна:просто извлекайте по одной строке единовременно, а затем добавляйте ее в список. Используйте объект-список из строк 16–17 на рис. 3 и фрагмент кода ниже:

// Preprocess the file to create a list of byte[] lines
List<byte[]> lineListByte = new List<byte[]>();
var lines = 
    FileMmByte.ReadLines(Path.Combine(Environment.CurrentDirectory, inFile));
// ... Multiple queries can use lineListByte
// ....
foreach (byte[] line in lines) { lineListByte.Add(line); }
// ....
var q = from line in lineListByte.AsParallel().
    WithDegreeOfParallelism(degreeOfParallelism)

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

Финальное увеличение производительности

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

В соответствующей реализации, Geonames IndexFields, метод ReadLines модифицирован так, чтобы возвращаемая строка была объектом, содержащим как массив byte[], так и массив uint[], в котором хранятся позиции каждого поля. Это повышает производительность примерно на 33% по сравнению с Geonames Index и приближает ее к производительности неуправляемого решения и решения на основе C#/.NET. (Geonames IndexFields включен в исходный код, который вы можете скачать.) Более того, теперь намного проще конструировать более универсальные запросы, поскольку индивидуальные поля уже известны.

Ограничения

Все эффективные решения требуют присутствия данных в памяти, и выигрыш в производительности не распространяется на очень большие наборы данных. Под «очень большими» здесь подразумеваются объемы данных, приближающиеся к объему системной физической памяти. В примере с Geonames файл размером 3302 Мб (четыре копии исходного файла) можно эффективно обработать в тестовой системе с 8 Гб оперативной памяти. Однако тест с восемью объединенными копиями файла выполнялся очень медленно при любых решениях.

Как уже упоминалось, производительность также достигает максимума, когда данные являются «актуальными» («live») в том смысле, что к ним было недавнее обращение и что они с большой вероятностью находятся в памяти. Подкачка из файла данных при начальном прогоне может отнять 10 и более секунд, что сравнимо с операцией индексации в предыдущем фрагменте коде.

В целом, результаты в этой статье относятся к структурам данных, находящимся в памяти, а нынешние цены на память и ее объемы вполне позволяют оперировать в памяти весьма значительными по объему объектами данных, в том числе файлом с 7,25 миллионами географических названий.

Дополнительные результаты

На рис. 5 показаны результаты тестов в дополнительной системе (Intel i7 860, 2,80 ГГц, 4 ядра, 8 потоков, Windows 7, 4 Гб RAM). Этот процессор поддерживает «гиперпоточность» (hyper-threading, HT), поэтому тестировались значения DoP от 1 до 8. Результаты на рис. 1 были получены в тестовой системе на процессоре AMD с шестью ядрами; в этой системе нет технологии HT.

image: Intel i7 860, 2.80GHz, Four Cores, Eight Threads, Windows 7, 4GB RAM

Рис. 5. Intel i7 860, 2,80 ГГц, 4 ядра, 8 потоков, Windows 7, 4 Гб памяти

Две дополнительные тестовые конфигурации дали аналогичные результаты (полный отчет см. на моем веб-сайте):

  • Intel i7 720, 1,60 ГГц, 4 ядра, 8 потоков, Windows 7, 8 Гб RAM;
  • Intel i3 530, 2,93 ГГц, 2 ядра, 4 потока, Windows XP64, 4 Гб RAM.

Стоит отметить интересные особенности.

  • Geonames Threads постоянно обеспечивало наилучшую производительность наряду с Geonames Native.
  • Geonames Index — самое быстрое решение на основе PLINQ, приближающееся по производительности к Geonames Threads. Примечание: GeonamesIndexFields чуть быстрее, но нарис. 5*.* не показано.
  • Кроме Geonames Index, все PLINQ-решения отрицательно масштабируются при DoP выше двух, т. е. производительность снижается по мере увеличения количества параллельных задач. В этом примере PLINQ дает неплохую производительность, только когда используется с индексированными объектами.
  • Вклад гиперпоточности в производительность пренебрежимо мал. Поэтому производительность Geonames Threads и Geonames Index увеличивается лишь весьма незначительно при DoP выше четырех. Такая слабая масштабируемость HT могла бы быть следствием планирования двух потоков на логических процессорах в одном ядре вместо того, чтобы гарантировать их выполнение на разных ядрах, когда такая возможность появляется. Однако это объяснение не выглядит правдоподобным, поскольку Марк Руссинович (Mark Russinovich), Дэвид Соломон (David Solomon) и Алекс Ионеску (Alex Ionescu) в своей книге «Windows Internals, Fifth Edition» (Microsoft Press, 2009) на с. 40 сообщают, что физические процессоры планируются до логических. Системы AMD без HT (рис. 1) давали производительность в 3-4 раза более высокую при DoP выше четырех по сравнению с последовательным выполнение (DoP=1) для Threads, Native и Index. Как показывает рис. 1, максимальная производительность достигается, когда DoP совпадает с числом ядер; при этом производительность многопоточного кода в 4,2 раза выше однопоточного.

Краткие итоги

PLINQ предоставляет отличную модель для обработки структур данных в памяти, и производительность существующего кода можно повысить внесением некоторых простых изменений (пример — Helper) или за счет более продвинутых методик, как в случае MMByte. Однако никакие простые усовершенствования не дают повышения производительности, близкого к таковой для неуправляемого или многопоточного C#/.NET-кода. Более того, такие усовершенствования не масштабируются с ростом числа ядер и значения DoP.

PLINQ может приближаться по производительности к неуправляемому и C#/.NET-коду, но это требует использования индексированных объектов данных.

Использование кода и данных

Весь код доступен на моем веб-сайте (jmhartsoftware.com/SequentialFileProcessingSupport.html), и для его скачивания следуйте этим инструкциям.

  • Перейдите на страницу для скачивания ZIP-файла, содержащего PLINQ-код и код Geonames Threads. Все PLINQ-вариации находятся в проекте GeonamesPLINQ (Visual Studio 2010; достаточно даже Visual Studio 2010 Express). Код Geonames Threads размещен в проекте Visual Studio 2010 — GeonamesThreads. Оба проекта сконфигурированы для компиляции под 64-разрядные ОС. В ZIP-файле также содержится электронная таблица с исходными данными, которые использовались в тестах на рис. 1, 2 и 5. Простой комментарий «Usage» в заголовке файла поясняет параметры командной строки для выбора входного файла, DoP и конкретной реализации.
  • Перейдите на страницу поддержки Windows System Programming (jmhartsoftware.com/comments_updates.html) для скачивания кода и проектов Geonames Native (ZIP-файл), где вы найдете проект Geonames. Структуру поясняет файл ReadMe.txt.
  • Скачайте базу данных GeoNames по ссылке download.geonames.org/export/dump/allCountries.zip.

Неизученные вопросы

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

  • Каково влияние промахов кеша, и есть ли какой-нибудь способ уменьшить это влияние?
  • Какой эффект даст применение дисков SSD (solid-state disks)?
  • Есть ли способ уменьшить разрыв в производительности между PLINQ-решением Index и решениями Threads и Native? Эксперименты с уменьшением объема данных, копируемых в методах FileMmByte, IEnumerator.MoveNext и Current, не дали сколько-нибудь значимого выигрыша.
  • Близка ли производительность к теоретическому максимуму для данной пропускной способности памяти, быстродействия процессора и других аппаратных средств?
  • Есть ли способ добиться масштабируемой производительности на HT-системах (рис. 5), сравнимой с системами без HT (рис. 1)?
  • Умеете ли вы использовать средства профилирования и другие инструменты Visual Studio 2010 для выявления и устранения узких мест?

Надеюсь, вы сможете продолжить это исследование.

Johnson (John) M. Hart — консультант, преподаватель, автор книг и статей.Специализируется на разработках и архитектурах приложений для Microsoft Windows и Microsoft .NET Framework. Имеет большой опыт работы в качестве инженера ПО, технического директора и архитектора в Cilk Arts Inc.(поглощена корпорацией Intel), Sierra Atlantic Inc., Hewlett-Packard и Apollo Computer. Параллельно многие годы был профессором в области компьютерных наук, автор четырех изданий «Windows System Programming» (Addison-Wesley, 2010).

Выражаю благодарность за рецензирование статьи экспертам Майклу Брюстле (Michael Bruestle),Эндрю Гринвалду (Andrew Greenwald), Трой Магеннис (Troy Magennis) и CK Park