УДК 004.56:004.65
Р. Ф. Гибадуллин, А. Г. Савельев, М. Ю. Перухин
УСКОРЕНИЕ ОБРАБОТКИ SQL-ЗАПРОСОВ К БАЗАМ ДАННЫХ НА GPU ПОСРЕДСТВОМ АППАРАТНО-ПРОГРАММНОЙ ПЛАТФОРМЫ NVIDIA CUDA
Ключевые слова: базы данных, графический процессор.
В данной статье основное внимание уделяется ускорению запросов на выборку и описываются аспекты эффективной реализации графическим процессором командного процессора SQLite. Результаты, показанные NVIDIA Tesla C1060, в зависимости от размера результирующего набора, достигали ускорений в 20-70 раз. В статье приводятся сравнения с альтернативными платформами исполнения запросов, разработанными в последнее время, такими как MapReduce, а также описаны будущие направления исследований в области обработки данных на GPU.
Keywords: database, GPU.
This paper focuses on accelerating SELECT queries and describes the considerations in an efficient GPU implementation of the SQLite command processor. Results on an NVIDIA Tesla C1060 achieve speedups of 20-70x depending on the size of the result set. This work is compared to the alternative query execution platforms developed recently, such as MapReduce implementations, and future avenues of research in GPU data processing are described.
Введение
Параллельная архитектура современных графических процессоров дает очень высокую пропускную способность в некоторых задачах, а близость к универсальному применению в настольных компьютерах означает, что они являются дешевым и повсеместным источником мощности. Наблюдается растущий интерес к использованию этой вычислительной мощности в общих неграфических задачах через такие механизмы, как NVIDIA CUDA. Интерфейс программирования разработан так, чтобы дать программистам простой и стандартный способ выполнять логику общего назначения на графических процессорах NVIDIA. Программисты часто используют CUDA и аналогичные интерфейсы для ускорения интенсивных вычислений по обработке данных, зачастую выполняя их в пятьдесят раз быстрее на GPU чем на CPU. Многие из этих операций имеют прямые параллели с классическими запросами к базе данных.
Сложная архитектура процессора делает трудным его использование в полной мере незнакомым с ней программистам. Чтобы эффективно использовать технологию CUDA, программист должен иметь представление о шести разных участках памяти, модели отображения нитей и блоков нитей в аппаратной модели GPU, иметь понимание технологии межнитевой коммуникации и т.д. CUDA вывела GPU-разработку на уровень ближе к мейнстриму, но по-прежнему программистам необходимо разрабатывать низкоуровневое ядро CUDA для каждой операции обработки данных, которые они планируют выполнять на GPU, что требует больших временных затрат и часто дублирует работу.
SQL является стандартизированным универсальным декларативным языком, который используется для манипуляции с базами данных и создания запросов. СУБД способна выполнять сложные процессы соединения и агрегирования наборов данных. SQL используется в качестве моста между процедурными программами и структурированными таблицами данных. Ускорение запросов SQL позволит
программистам увеличить скорость выполнения операций по обработке данных с небольшим изменением исходного кода. Несмотря на спрос программного ускорения на GPU не существует реализации SQL, способной к автоматическому доступу к GPU, даже при том, что SQL-запросы были эмулированы на GPU, чтобы доказать адаптируемость параллельной архитектуры к таким паттернам выполнения.
Существует ряд ограничений, накладываемых GPU, которые оказывают влияние на развитие GPU SQL. Два наиболее существенных ограничения - это ограниченный размер памяти GPU и время передачи данных с CPU на GPU и обратно. Передача блоков памяти между CPU и GPU также является затратным. Несмотря на эти ограничения, фактическое выполнение запроса может быть выполнено параллельно на GPU, выиграв по времени исполнения у CPU.
Вклад данной работы заключается в реализации и демонстрации интерфейса SQL для GPU. Этот интерфейс включает подмножество запросов SELECT к данным, которые были переданы в виде строк и столбцов в память GPU. Запросы SELECT были выбраны, так как это наиболее распространенные SQL-запросы, и их read-only особенность в наибольшей степени подходит для выполнения на GPU. Проект создан на СУБД с открытым исходным кодом и допускает переключение между CPU и GPU, позволяя увидеть сравнение последовательного и параллельного выполнения.
SQLite
SQLite - это СУБД с полностью открытым исходным кодом, разрабатываемая маленькой командой, которую поддерживают некоторые крупные корпорации. Команда разработчиков утверждает, что SQLite самая широко используемая СУБД в мире, благодаря применению в популярных приложениях, таких как Firefox, и на мобильных устройствах, таких как iPhone. SQLite ценят за ее чрезвычайную простоту и тщательное тестирование. В от-
личие от большинства баз данных, которые работают как сервер, доступных отдельным процессом и обычно доступных удаленно, SQLite создавалась, чтобы быть скомпилированной прямо в исходном коде клиентского приложения. SQLite распространяется в виде одного исходного файла Си, делая тривиальным добавление базы данных с полной поддержкой SQL в приложения С\С++.
Архитектура SQLite является относительно простой, и краткое описание необходимо для понимания реализации на CUDA, которая описана в этой статье [1]. Ядро инфраструктуры SQLite содержит пользовательский интерфейс, командный процессор SQL, а так же виртуальную машину. SQLite содержит так же обширные функциональные возможности для обработки дисковых операций, распределения памяти, тестирования и т.д. Пользовательский интерфейс состоит из библиотеки функций Си и структур для обработки операций, таких как инициализация базы данных, выполнение запросов и просмотр результата. Интерфейс прост и интуитивно понятен: можно открыть базу данных и выполнить запрос вызвав всего две функции. Функции используют командный процессор SQL. Функция командного процессора подобна компилятору: она содержит токениза-тор, синтаксический анализатор и генератор кода. Анализатор создается с генератором синтаксических анализаторов LALR(1) под названием Lemon, очень похожий на YACC и Bison. Процессор команд выводит программу на промежуточном языке, близкой к ассемблеру. По своей сути, процессор принимает сложный синтаксис запроса SQL и выводит набор дискретных шагов [2,3].
Каждая операция в этой промежуточной программе содержит свой операционный код и до пяти аргументов. Каждый опкод относится к конкретной операции, выполняемой в базе данных. Опкоды выполняют такие операции, как открытие таблицы, загрузка данных из ячейки в регистр, выполнение математических операций в регистре, и прыжок на другой опкод. Простой запрос SELECT инициализирует доступ к таблице базы данных, запуская цикл по каждой строке, затем происходит очистка и выход. Цикл включает в себя опкоды, такие как Column, который загружает данные из столбца текущей строки и помещает его в регистр, ResultRow, который перемещает данные в наборе регистров для результирующего набора запроса, и Next, который перемещает программу на следующую строку.
Эта программа операционного кода выполняется с помощью виртуальной машины SQLite. Виртуальная машина управляет открытыми базами данных и таблицами, и хранит информацию в наборе «регистров».
Программная реализация
Учитывая диапазон обоих запросов к базе данных и приложений баз данных, а также ограничения разработки на CUDA, был определен объем нашей работы. Наша основная цель получить приложение, которое выполняет несколько раз SELECT-запрос на одном и том же наборе данных среднего размера. Это означает, что GPU использует данные только
для чтения. Это позволяет GPU максимизировать пропускную способность и утверждает хранение базы данных в виде строк столбцов. "Многократно" означает, что программа была разработана таким образом, что SQL-запросы выполняются на данных, уже находящихся на карте. Основным препятствием для обработки данных на GPU является затраты на перемещение данных между карточкой и памятью компьютера. Перемещая блок данных в память GPU и выполняя несколько запросов, затраты на загрузку данных амортизируются, поскольку мы выполняем все больше и больше запросов, при этом уменьшая затраты. И, наконец, для "набора данных среднего размера" достаточно данных, чтобы проигнорировать издержки на создание вызова ядра CUDA, но меньше, чем верхняя граница общей памяти GPU. На практике, было разработано и испытано выполнение одного и пяти миллионов наборов данных строк.
В этой работе осуществлена поддержка только числовых типов данных. Хотя строки и BLOB, безусловно, очень полезные элементы SQL, на практике серьезный анализ неструктурированных данных часто проще реализовать с другой парадигмой. Строки также нарушают расположение данных в колонках фиксированной ширины, используемой в этой работе, и передача указателей символов от хоста к устройству является утомительной операцией. Числовые типы данных, включают 32-битные целые, 32-битные значения с плавающей запятой стандарта IEEE 754, 64-битные целые числа, и 64-битные значения двойной точности стандарта IEEE 754. Ослабление этих ограничений является областью для будущей работы.
Предполагается, что данные остаются постоянно на карточке в течение нескольких запросов и, таким образом, можно пренебречь верхним пределом издержек на перемещение данных на GPU. Основываясь на запросах SQL типа read-only и характеристик модели программирования CUDA, данные хранятся на GPU в виде строк столбцов. SQLite хранит данные в B-дереве, таким образом, требуется перевод. Для удобства этот процесс выполняется в SQLite с помощью SELECT-запроса для извлечения подмножества данных из текущей открытой базы данных.
GPU Tesla C1060, используемый для разработки, имеет 4 гигабайта глобальной памяти. Таким образом, устанавливается верхний предел размера входных данных, без перемещения данных во время выполнения запросов на карту и с нее. Следует отметить, что в дополнение к набору данных загруженных на GPU, должен быть другой блок памяти, выделяемый для хранения результатов. Оба эти блока выделяются во время инициализации программы. Кроме того, выделяются метаданные, такие как размер блока, количество строк в блоке, шаг блока, и размер каждого столбца.
Во время фазы инициализации нам необходимо выделить 4 блока памяти: блок данных и блок результатов центрального и графического процессоров (CPU и GPU). Блоки центрального и графического процессоров одного типа имеют одинаковый размер, но блоки данных и результата могут быть раз-
ных размеров. Эта может быть преимуществом в тех случаях, когда блок данных больше, чем 50% доступной памяти в графическом процессоре, но результирующий набор, как ожидается, будет достаточно малым. Например, если отношение результата записей данных, как ожидается, будет 1:4 , тогда 80% памяти графического процессора может быть выделено для передачи данных, и 20% выделено на результат.
Так как данные должны быть переданы и из GPU, время передачи в память значительно влияет на производительность. CUDA API обеспечивает возможность использовать pinned-память для передачи памяти как можно быстрее. Наша реализация использует pinned-память для блоков данных и результатов. Pinned-память в целом увеличивает скорость передачи памяти в 2 раза и должна быть использована таким образом всякий раз, когда это возможно с большими блоками данных. К сожалению, использование значительных объемов pinned-памяти часто влияет на производительность других процессов в компьютере.
Пространства памяти
В работе мы пытаемся использовать иерархию памяти CUDA в полной мере, используя регистровую, разделяемую, константную, локальную и глобальная память. Регистровая память содержит информацию о конкретных нитей, такая как смещения в блоках данных и результатов. Разделяемая память используется совместно всеми нитями в блоке, используется для координации нитей во время фазы сокращения выполнения ядра, и каждая нить со строкой результата должна поместить в нее в уникальном месте в наборе данных результата. Константная память особенно полезна для данной работы, так как она используется для хранения программы опкода выполняемой каждой нитью. Она также используется для хранения метаинформации набора данных, включая тип и ширину столбца. Так как к программе и этой информации о наборе данных обращаются очень часто все нити, константная память значительно снизит издержки, которые могли бы быть, если эта информация хранилась в глобальной памяти.
Глобальная память обязательно используется для хранения набора данных, на котором выполняется запрос. Глобальная память имеет значительно более высокую латентность, чем регистровая или константная памяти, таким образом, никакой информации, кроме всего набора данных в глобальной памяти не хранится с одним эзотерическим исключением. Локальная память представляет собой абстракцию в модели программирования CUDA, что означает, что эта память, в пределах одной нити, хранится в глобальном пространстве памяти. Каждый блок нитей CUDA ограничен 16 килобайтами регистровой памяти: когда этот предел наступает компилятор автоматически помещает переменные в локальную память.
Локальная память также используется для массивов, доступ к которым осуществляется из переменных, не известных во время компиляции. Это
существенное ограничение, так как регистры виртуальной машины SQLite хранятся в массиве. Это ограничение обсуждается более подробно ниже.
Анализ запросов
Как уже говорилось выше, SQLite анализирует SQL запрос в программе операционного кода, напоминающий код ассемблера. В этой работе вызывается команда процессора SQLite и извлекает результаты, удаляя лишние данные на подмножестве SQL-запросов, реализованных в данной работе. Фаза обработки так же используется для готовности передачи опкода программы в GPU, в том числе разыме-новывание указателя и сохранение цели непосредственно в программе опкода [4,5].
Выполнение опкода виртуальной машиной приводит к последовательной итерации всей таблицы и как результат выдаёт строку. Не все операционные коды в этой работе хранятся в одной таблице памяти GPU и в результате не выполняются. Ключ к такого рода процедурам заключается в том, что операционные коды управляются программным счетчиком и переходят в разные места, таким образом операционные коды не всегда используются по порядку. Например, опкод Next продвигается от одной строки к следующей и переходит к значению второго параметра.
Практически все коды операций воздействуют с массивом регистров SQLite в некотором роде. Регистры - универсальные элементы памяти, которые могут сохранить любой вид данных и индексированы в массиве. Опкод Column отвечает за загрузку данных из столбца в текущей строке в определенный регистр. Опкод Integer устанавливает значение регистра к определенному целому числу.
Инфраструктура виртуальной машины
Сутью этой работы является доработка виртуальной машины SQLite с помощью CUDA. Виртуальная машина реализована как ядро CUDA, которое выполняет процедуру опкода. В работе реализованы около 40 опкодов, которые охватывают опко-ды сравнения, таких как больше или равно, математические коды операций, таких как добавление, логические коды операций, например, «Побитовое ИЛИ», побитовые коды операций, такие как «Побитовое И» , а также несколько других важных опкодов, таких как ResultRow. Коды операций сохранены в двух операторах переключения.
Первый оператор переключения виртуальной машины позволяет расходящееся выполнение кода операции, а второй требует параллельного выполнения кода. Иными словами, первый оператор переключения дает нитям выполнять разные коды операций одновременно. Когда встречается опкод Next, означающий конец параллелизму, зависящего от данных, виртуальная машина перескакивает из расходящихся блоков в параллельные блоки. Параллельный блок используется для функции агрегирования, где важна координация через все потоки.
Главная часть ядра CUDA - редукция при вызове опкода ResultRow множеством нитей для выгрузки строк результата. Так как не каждая строка выгру-
жает строку, необходима операция редукции, чтобы гарантировать, что блок результата представляет собой непрерывный набор данных. Редукция включает в себя межнитевую и межблочную коммуникацию, поскольку каждой нити, которая должна выгрузить строку, должна быть присвоена уникальная область результирующего блока данных. Несмотря на то, что результирующий набор непрерывен, не гарантируется порядок результатов.
Редукция осуществляется с помощью атомарной операции CUDA atomicAdd(), вызываемой на двух уровнях. Во-первых, все данные последовательно вызывают atomicAdd() на переменную в разделяемой памяти, тем самым, получив присваивание в блоке нитей. Последняя нить в блоке вызывает эту функцию на отдельную глобальную переменную, которая определяет позицию блока нитей в пространстве памяти, которое каждая нить использует, чтобы определить его точную целевую строку на основе предыдущего присвоения в блоке нитей. Экспериментально установлено, что этот метод редукции быстрее чем другие для этого конкретного типа присвоения, особенно с редкими результирующими наборами.
В этой работе также осуществлена поддержка функций агрегирования SQL (т.е. COUNT, SUM, MIN, MAX и AVG), хотя это только для целочисленных значений. Значительные усилия были предприняты, чтобы придерживаться плана запроса проанализированного SQLite без использования нескольких запусков ядра. Так как межнитевая блочная координация, которая используется для выполнения функций агрегации, трудна без использования запуска ядра в качестве глобального барьера, атомарные функции используются для координации, но они могут быть использованы только с целыми значениями в CUDA. Это ограничение, как ожидается, будет устранено в аппаратных средствах нового поколения, а также производительность для целочисленных данных агрегируют, вероятно, хорошее приближение будущей производительности для других типов.
Как уже говорилось, мы присваиваем строку таблицы для каждой нити. В нашей реализации используется 192 нити в каждом блоке. CUDA позволяет использовать максимум 65 536 блоков нитей в одном измерении, таким образом, мы столкнулись с проблемой обработки более чем 12,582,912 строк. Была реализована функциональность, в которой нить обрабатывает множество строк, если размер данных превышает это число, несмотря на то, что это несет издержки производительности из-за увеличения расхождения потока.
Результирующий набор
После того, как виртуальная машина отработала, результирующий набор по-прежнему находится на GPU. Хотя скорость выполнения запроса может быть измерена по времени работы виртуальной машины, на практике результаты должны быть перемещены обратно в CPU, чтобы использоваться в хост-процессе. Это реализуется в виде двухстадий-ного процесса. Сначала хост передает блок инфор-
мации о результирующем наборе обратно из GPU. Эта информация содержит шаг строки результата и число строк результата. CPU умножает эти значения для определения абсолютного размера результирующего блока. Если есть нулевые строки, то копия памяти результата не нужна, в противном случае копия памяти используется для передачи результирующего набора. Поскольку мы знаем точно, насколько большой набор результатов, то нам не нужно передавать весь блок памяти, выделенной для результирующего набора, тем самым сильно экономя время.
Набор данных
Данные, используемые для тестирования производительности, имеют пять миллионов строк со столбцом для идентификатора, тремя целочисленными столбцами и тремя столбцами с плавающей точкой. Данные были получены с использованием функционала генерации случайных чисел библиотеки GNU Scientific Library (или GSL). Один из столбцов каждого типа данных имеет равномерное распределение в интервале [-99.0, 99.0], один столбец имеет нормальное распределение со стандартным отклонением с = 5, и последний столбец имеет нормальное распределение со стандартным отклонением с = 20. Были протестированы целочисленные типы данных и типы данных с плавающей точкой. Случайные распределения обеспечивают непредсказуемые результаты обработки данных и означают, что размер результирующего набора варьируется в зависимости от критериев запроса на выборку.
Чтобы проверить производительность, были написаны 13 запросов. Пять из тринадцати - запросы целочисленных значений, пять - запросы значений с плавающей точкой, и последние 3 - тестирование функций агрегации. Запросы были выполнены через виртуальную машину CPU SQLite, а затем через виртуальную машину GPU, после чего сравнивалось время выполнения операций. Также был рассмотрен объем времени, необходимый для передачи результатов GPU с устройства на хост. Показан размер результирующего набора в строках для каждого запроса, так как это существенно влияет на производительность запросов. Запросы были выбраны для демонстрации гибкости реализованных возможностей запросов и обеспечить широкий диапазон вычислительной интенсивности и размера результирующего набора.
У нас нет оснований полагать, что результаты будут существенно меняться с реальными наборами данных, так как все строки проверяются SELECT-запросом, и производительность сильно коррелирует с количеством возвращаемых строк.
Аппаратное и программное обеспечение
Результаты производительности были собраны с машины Intel Xeon X5550 с установленной операционной системой Linux 2.6.24. Процессор - 64-разрядный четырехъядерный с частотой 2.66 ГГц, поддерживающий 8 аппаратных нитей с максимальной пропускной способностью 32 Гб/сек. Машина имеет 5 гигабайт памяти. Используется видеокарта NVIDIA Tesla C1060. Tesla имеет 240 ядер CUDA, 4
Гб глобальной памяти и поддерживает максимальную пропускную способность в 102 Гб/сек.
В ходе тестирования машина использовала драйверы CUDA 2.2. Для сравнения производительности использовалась SQLite 3.6.22, для компиляции - Intel C Compiler 11.1 с включенной оптимизацией. Компилятор NVIDIA CUDA (NVIDIA CUDA compiler, nvcc) использует компилятор GNU C (GNU C Compiler) для использования кода на языке С в файлах CUDA. Таким образом, часть кода, которая непосредственно вызывает ядро CUDA, была скомпилирована с помощью GCC 4.2.4.
Чистота сравнения
Было приложено много усилий для получения объективных результатов сравнения настолько, насколько это возможно.
Данные на стороне процессора были явно загружены в память, тем самым, исключая обращения к диску при выполнения запроса. SQLite имеет функциональные возможности объявления временной базы данных, которая существует только в памяти. После инициализации, набор данных подключен и назван. Без этого шага реализация GPU почти в 200 раз быстрее, но это делается для более справедливого сравнения: это означает, что данные будут загружены полностью в память для обоих CPU и GPU.
SQLite была скомпилирована с помощью Intel C Compiler версии 11.1. Она оптимизирована флагами -О2, базовый флаг оптимизации, -xHost, который включает оптимизацию процессора, и -ipo, запускающий оптимизацию по исходным файлам. Это заставляет SQLite быть настолько быстрой, насколько возможно: без оптимизации SQLite работает значительно хуже.
Директивы выдаются SQLite во время компиляции, чтобы опустить защиту всех нитей и хранить все временные файлы в памяти, а не на диске. Эти директивы снижают накладные расходы на запросы SQLite.
Результаты запроса хоста не сохраняются. В SQLite результаты возвращаются, передавая функцию обратного вызова с запросом SQL. Она устанавливается в нулевое значение, что означает, что результаты запроса хоста выбрасываются, в то время как результаты запроса устройства явно сохраняются в памяти. Это ускоряет выполнение SQLite.
В таблице 1 приведены средние результаты для пяти целых запросов, пяти запросов с плавающей точкой, трех запросов агрегации, и всех остальных запросов. В столбце «Возвращенные строки» показано среднее число выходных строк результирующего набора во время выполнения запроса, которое принимает значение «1» для данных агрегирующих функций, поскольку реализованные функции сокращаются до одного значения по всем строкам набора данных. Среднее увеличение скорости во всех запросах было в 50 раз, которое уменьшилось до 36, когда было учтено время передачи результатов. Это означает, что в среднем, запуск запросов на наборе данных, загруженных в GPU, и передача результирующего набора назад были в 36 раз быстрее, чем выполнение запроса на CPU через SQLite. Числа для всех строк вычисляются с суммированием времен-
ных столбцов, и являются, таким образом, взвешенными по времени.
Таблица 1 - Данные об эффективности по типу запроса
Запрос Ускорение Ускорение с передачей Время CPU (с)
Целочисл. 42.11 28.89 2.3843
С плав. т. 59.16 43.68 3.5273
Агрегация 36.22 36.19 1.0569
Все 50.85 36.20 2.2737
Запрос Время GPU (с) Время передачи (с) Возвращено строк
Целочисл. 0.0566 0.0259148 1950104.4
С плав. т. 0.0596 0.0211238 1951015.8
Агрегация 0.0292 0.0000237 1
Все 0.0447 0.0180920 1500431.08
Рис. 1 графически показывает ускорение и ускорение с учетом времени передачи тестируемых запросов. Нечетные запросы являются целочисленными запросами, четные запросы - запросы с плавающей точкой, а последние 3 запроса являются агрегирующими вызовами. График показывает значительные отклонения в значениях ускорения в зависимости от специфики запроса. Спаривание двух измерений ускорения также демонстрирует значительное количество времени, которое некоторые запросы, такие как запрос 6, тратят на передачу результирующего набора. В других запросах, таких как запрос 2, разница очень мала. Все агрегирующие запросы показали довольно средние результаты, но незначительное время передачи результатов, так как все использованные агрегирующие функции сводятся к одному результату. Эти функции были проведены по всему набору данных, таким образом, ускорение демонстрирует время, которое требуется, чтобы сократить пять миллионов строк до одного значения.
Рис. 1 - Ускорение выполнения каждого из 13 рассмотренных запросов на GPU, включая и, исключая время передачи
Время, необходимое для передачи набора данных из памяти хоста SQLite в память устройства, занимает около 2.8 секунд. Эта операция такая дорогая, потому что данные извлекаются из SQLite с помощью запроса и помещаются в вид строки-столбца, таким образом, они копируются несколько раз. Это необходимо, поскольку SQLite хранит данные в форме B-дерева, в то время как виртуальная машина GPU этого проекта ожидает данные в виде строки-столбца. Если бы эти два вида были идентичны, дан-
ные могли бы передаваться напрямую из хоста на устройство за время, сравнимое со временем передачи результата. В таком случае многие GPU-запросы были бы быстрее, чем запросы CPU, даже с учетом времени передачи данных, времени выполнения запроса и времени передачи результата. Как описано выше, мы допускаем, что запросы выполняются на одних и тех же наборах данных, и игнорируем эти затраты так же, как игнорируем затраты на загрузку файла базы данных с диска в память SQLite.
Интересно, что запросы с плавающей точкой имеют чуть большее ускорение, чем целочисленные запросы. Вероятно, это результат обработки целых чисел на GPU. Тогда как GPU поддерживает операции с плавающей точкой, совместимые с IEEE 754, целочисленная математика выполняется с 24-разрядным модулем, таким образом, 32-разрядные целочисленные операции эмулированы. Полученная разница в производительности не является тривиальной, но не достаточно большой, чтобы изменить величину ускорения. Следующее поколение аппаратных средств NVIDIA, как ожидается, будет поддерживать 32-разрядные целочисленные операции.
Зависимость от размера данных
Исследование ускорения запроса как функция размера набора данных приводит к интересным результатам. На рис.2 показано ускорение для набора запросов, в которых запрос остается постоянным, но меняется размер таблицы. Запрос SELECT запрос был запущен для выбора каждого члена универсального целочисленного столбца со значением меньше 0, примерно половины набора. Тестирование установленных наборов данных с установленным размером шага в 5000 строк в диапазоне от 0 до 500000 дает отображаемые результаты. При коли-
Строки (тысяч)
Рис. 2 - Влияние размера набора данных на ускорение выполнения запроса на GPU, включая и, исключая время передачи
честве строк меньше 50000, ускорение резко возрастает, поскольку графический процессор более насыщен работой и способен более эффективно планировать выполнение нитей. Стоит отметить, что ускорение потом медленно увеличивается до примерно 200 000 строк, где становится постоянной. Дальнейшие испытания подтвердили эту тенденцию. Несмотря на то, что только два столбца выбираются в нашем запросе, было выполнено тестирование на множестве данных, описанных выше, которые содержат 4 целочисленных столбца и 3 столбца с плавающей точкой, при общей ширине 28
байт. Таким образом, для этой таблицы конкретного запроса, мы не достигнем оптимальной производительности на данных размером менее 5 Мб. Однако следует отметить, что точки, в которых выполняются запросы на GPU располагаются довольно низко, относительно разрешения этого графика.
Примечательно то, как низко располагаются точки рентабельности выполнения запроса на GPU с точки зрения размера данных. Тестируя один и тот же запрос выборки каждого члена универсального целочисленного столбца меньше 0, увеличивая набор данных шагом в 10 строк, скорость выполнения GPU соответствовала скорости выполнения CPU с набором данных около 350 строк. Эти результаты показаны на рис. 3, в котором графики скорости выполнения GPU относительно выполнения CPU в более высоком разрешении, чем на рис. 2. Это означает, что ключевая точка, где происходит увеличение скорости равна 1. Очевидно, что эта точка немного отличается, когда время передачи результатов включено в расчеты. Рентабельность 350 лишь немного больше, чем 240 мультипроцессоров Tesla,
Строки
Рис. 3 - Точка рентабельности в строках для выполнения запроса, включая и, исключая время передачи
используемых для тестирования, а также указывает на то, что выполнение запроса на GPU быстрее, чем выполнение на CPU даже при очень небольших наборов данных, и даже с учетом времени, необходимого для передачи результатов обратно. Несмотря на то, что каждый запрос по разному ускоряется на GPU, это низкое значение указывает на то, что ожидаемая точка рентабельности даже для самого тяжелого запроса по-прежнему довольно низка. Существует очень мало наборов данных меньших 350 строк, для которых скорость выполнения запроса является проблемой, предполагаем, что GPU является хорошим подходом для почти всех запросов SELECT кроме поиска равенства.
Зависимость от размера результата
Есть несколько основных факторов, которые влияют на результаты ускорения отдельных запросов, в том числе трудности каждой операции и размер результата. Хотя современные CPU работают на тактовых частотах свыше 2 ГГц и используют чрезвычайно оптимизированные и глубоко конвейерные АЛУ, тот факт, что эти операции распараллелены на более 240 ядер CUDA означает, что GPU должны превосходить в этой области, несмотря на тот факт,
что SM гораздо менее оптимизированы на индивидуальном уровне. К сожалению, сложно измерить вычислительную мощность запроса, но стоит заметить, что запросы 7 и 8, которые включают операции умножения, выполнялись на одном уровне с остальными запросами, несмотря на то, что умножение - довольно дорогая операция.
Более значительным фактором ускорения запроса был размер результирующего набора, другими словами, количество строк, которое вернул запрос. Это имеет значение, поскольку больший результирующий набор увеличивает затраты на стадию редукции, так как каждая нить должна вызвать а^тг-cAdd(). Это также напрямую воздействует на то, как долго необходимо копировать результирующий набор из памяти устройства в память хоста. Эти факторы освещены на рис. 4. Был выполнен набор из 21 запроса, строки данных, в которых возвращались, когда колонка unгformг была меньше, чем х, где х -значение из диапазона [-100, 100] с инкрементом 10 для каждого последующего запроса. Так как колонка uniformi содержит равномерное распределение целых чисел в диапазоне от -99 до 99, ожидаемый размер результирующего набора возрос на 25000 для каждого запроса в пределах от 0 до 5000000.
Возвращено строк (млн.)
Рис. 4 - Влияние размера набора результата на ускорение выполнения запроса на GPU, включая и, исключая время передачи
Наиболее яркой тенденцией этого графика является то, что ускорение выполнения запросов GPU увеличилась вместе с размером результирующего набора, несмотря на затраты на редукцию. Это показывает, что реализация GPU более эффективна при обработке результирующей строки, чем реализация CPU, возможно, из-за огромной пропускной способности устройства. Затраты на передачу результирующего набора назад продемонстрированы на второй линии, которая постепенно отклоняется от первой, но все еще устремляется вверх, показывая, что реализация GPU все еще более эффективна, когда рассматривается время передачи строки назад. Для этих тестов невзвешенное среднее время передачи
© Р. Ф. Гибадуллин - к.т.н.; доцент кафедры компьютерных систем Казанского национального исследовательского технического университета им. А.Н. Туполева - КАИ (КНИТУ-КАИ), [email protected]; А. Г. Савельев - аспирант; ассистент той же кафедры, [email protected]; М. Ю. Перухин - к.т.н.; доцент кафедры автоматизированных систем сбора и обработки информации КНИТУ. [email protected].
© R. Gibadullin - Dr. (PhD) associate professor of computer system department of Kazan National Research Technical University named after A.N.Tupolev - KAI (KNRTU-KAI), [email protected]; А. Savelyev - postgraduate; assistant the same Department, [email protected]; М. Peruhin - Dr. (PhD) associate professor of automated systems for the collection and processing of information department of KNRTU, [email protected].
одиночной 16-разрядной строки (включая мета-информацию и затраты на настройки копирования памяти) было 7.67 нс. Обратите внимание, что точка данных для 0 возвращаемых строк выбивается из ряда. Это происходит, потому что передача результатов назад происходит в два этапа, как описано в разделе о реализации, и второй шаг не требуется, когда результирующие строки отсутствуют. Эта особенность таким образом показывает, как высоки затраты на использование атомарных операций в фазе редукции и инициацию операции копирования памяти в фазе передачи результата.
Заключение
Эта работа демонстрирует вычислительную мощь обработки данных на графическом процессоре с помощью типового интерфейса и подтверждает эффективность выполнения запросов на GPU для ускорения операций с базами данных. Несмотря на то, что использовалось лишь небольшое подмножество всех возможных запросов SQL, результаты являются многообещающими, и есть основания полагать, что все возможные запросы SELECT способны достичь аналогичных результатов. Использование SQL несет собой разрыв парадигмы предыдущих исследований, которые управляли запросами на GPU с помощью операционных примитивов, таких как отображение, преображение или сортировка. Кроме того, это значительно снижает затраты на использование графических процессоров для ускорения вычислений с базами данных. Результаты этой работы показывают, что реализация вычислений баз данных на GPU является перспективной областью для будущих исследований, а также коммерческого развития.
Литература
1. A. Abouzeid, K. Bajda-Pawlikowski, D. Abadi, A. Silberschatz, and A. Rasin. Hadoopdb: an architectural hybrid of ma-preduce and dbms technologies for analytical workloads. Proc. VLDB Endow., 2(1):922{933, 2009.
2. P. Bakkum and K. Skadron. Accelerating sql database operations on a gpu with cuda. In GPGPU '10: Proceedings of the 3rd Workshop on General-Purpose Computation on Graphics Processing Units, pages 94{103, New York, NY, USA, 2010. ACM.
3. N. Bandi, C. Sun, D. Agrawal, and A. El Abbadi. Hardware acceleration in commercial databases: a case study of spatial operations. In VLDB '04: Proceedings of the Thirtieth international conference on Very large data bases, pages 1021-1032. VLDB Endowment, 2004.
4. Вершинин И.С. Стойкость ассоциативной защиты к атаке со знанием открытого текста // Вестник Казан. технол. ун-та. - 2014. - № 11 - С. 218-220.
5. Вершинин И.С., Гибадуллин Р.Ф., Пыстогов С.В., Перу-хин М.Ю. Импорт/экспорт ассоциативно защищенных картографических данных с их обработкой в системе Security Map Cluster // Вестник технол. ун-та. - 2015. - № 10 - С. 174-180.