Perl5 как язык для изучения данных
Введение в серию – Пост 0 из N
Эта статья является первой в серии, документирующей совместную разработку движка векторных баз данных (VDBE), написанной полностью на Perl5 + PDL. Позже посты проходят через каждый компонент этого двигателя; этот устанавливает ступень. Основной импульс для этой серии заключается не в том, чтобы вы сбросили свой VDBE, поскольку я не предъявляю претензий к производительности, а в том, чтобы показать, как можно использовать Perl для достижения практически всего, чего вы можете достичь с помощью любого другого языка, но умнее!
1. Почему Perl5 для Data Science?
Когда специалисты по изучению данных обсуждают выбор языка, разговор быстро сходится на Python, R или Julia. Perl5 редко занимает место за столом, но он имеет убедительный набор признаков, которые заслуживают второго взгляда. Эти черты не существенно изменились за эти годы (Perl5 всегда был таким!), но Если вы не были подвержены языку и не научились ценить его красоту, рациональность, гибкость, выразительность и на самом деле использовали его для продвижения своей работы вперед, вы бы не знали, что эти функции не только бесплатны с Perl5, но и могут помочь вам продвигать ваши проекты вперед.
Развертывание с нулевой установкой
Perl5 поставляется в качестве компонента по умолчанию практически для каждой UNIX-подобной операционной системы – дистрибутивы Linux, macOS, BSD и многие встроенные среды Linux – все это включает в себя работу perl бинарные из коробки. Python продвигается здесь, но все еще часто можно найти автономные серверы, сетевые устройства или узлы входа в HPC, где присутствует Perl, а полный стек Python – нет. Конвейер данных, записанный в Perl, может работать в первый день без conda окружающая среда, venvили контейнер.
Переносимость от центра обработки данных к границе
Тот же сценарий, который анализирует терабайтный набор данных на 256-ядерном узле HPC, может с незначительными изменениями конфигурации работать на Raspberry Pi, шлюзе IoT или встроенном контроллере. Однобинарная модель развертывания Perl и низкие накладные расходы на выполнение делают ее подлинной “однократная запись, запуск в любом месте” язык в среде, где переводчик Питона над головой или время разминки JIT Джулии было бы неприемлемым.
Если вы планируете развертывать в любом месте, и everywhere Perl5 – ваш очевидный выбор.
Наследие, основанное на обработке текста и данных
Перл был разработан с нуля для обработки текста, регулярных выражений и “клей” Работа между компонентами системы. На практике в конвейерах научных данных преобладают не численные вычисления, а смена данных: чтение разнородных форматов файлов, очистка беспорядочных записей, объединение наборов данных из разных источников и маршрутизация результатов до последующих потребляющих компонентов.
Ядро регулярного выражения Perl остается одним из самых мощных доступных, и один-линейки могут выполнять задачи очистки данных, которые потребуют вспомогательных библиотек на других языках.
Если вы находитесь в области научных вычислений, вы, возможно, столкнулись с понятием системы управления рабочим процессом и воспроизводимые исследования. Они оба полагаются на выполнение сквозных преобразований данных и рабочих процессов, чтобы устранить ручную, подверженную ошибкам и утомительную деятельность, которую аналитики и ученые должны выполнять, чтобы превратить свои данные в аналитику и выводы соответственно.
В этом смелом новом мире богатая история Perl5 позволяет ему сиять как в качестве компонента рабочих процессов, так и в качестве языка приложения, который реализует эти рабочие процессы.
CPAN: испытанная битвой экосистема модулей
Комплексная сеть архивов Perl (CPAN) содержит более 200 000 модулей по всем возможным доменам. Хотя предложения по изучению данных не так обширны, как Python, основные компоненты для выделенных разработчиков существуют:
PDL (Perl Data Language) – векторизованные числовые вычисления с сильно типизированными N-мерными массивами (покрытыми ниже).
PDL::Stats – описательная статистика, регрессия, кластеризация (k-средства, мини-пакетные k-средства) и многое другое, построенное на основе PDL ndarrays.
AI::MXNet, AI::TensorFlow – привязки для глубокого обучения.
Статистика::Регрессия, Статистика::Описание – классическая статистика без зависимости PDL.
Text::CSV, Spreadsheet::XLSX, Data::MessagePack, Sereal – высокопроизводительная сериализация и ввод-вывод.
DBI + десятки драйверов баз данных – доступ SQL к каждой крупной СУРБД.
MCE (Many-Core Engine) – структурированный параллелизм для рабочих нагрузок с общей и распределенной памятью.
Inline::C, Inline::CPP – встраивание кода C или C++ непосредственно в исходный файл Perl; компилятор вызывается прозрачно при первом запуске сценария, что делает тривиальным удаление критически важных ядер производительности в программу чисто Perl без полной системы сборки XS.
- FFI::Platypus – функции вызова в любой общей библиотеке (
.so/.dylib/.dll) из Perl без написания одной строки кода клея XS или C. Platypus поддерживает все типы C-эквивалентов, структуры, обратные вызовы и закрытия, а также является современным способом связывания Perl с BLAS, LAPACK, HDF5 или любой другой собственной библиотекой.
Современный перл не является перлом вашего деда
Приведенные ниже функции взяты непосредственно из официальных примечаний к выпуску (perl5360delta, perl5380delta, perl5400delta) и организованы путем выпуска, в котором они достигли стабильного статуса или были впервые введены. Выделены только функции, относящиеся к научным и научно-вычислительным рабочим нагрузкам.
Перл 5.36 – май 2022
use v5.36– набор функций теперь автоматически включаетuse warningsв дополнение кuse strict. Это также отключаетindirectсинтаксис вызова метода иmultidimensionalхэш-ключевое моделирование, устраняющее два общих источника тонких ошибок.
Именованные подпрограммные подписи (стабильные с 5.36; экспериментальные с 5.20) – параметры функции теперь объявляются по имени, с необязательными значениями по умолчанию. The //= и ||= операторы значения по умолчанию добавлены к подписям в версии 5.38, что позволяет использовать значения по умолчанию, которые запускаются undef или ложь соответственно:
use v5.36;
sub clamp ($val, $lo = 0, $hi //= 1) {
$val < $lo ? $lo : $val > $hi ? $hi : $val;
}
isaоператор class-instance(стабильный с 5.36; введен в 5.32) –$obj isa "ClassName"возвращает логическое значение; чище, чемref($obj) eq "ClassName".builtinмодуль (стабильный с 5.40; экспериментальный с 5.36) – лексически импортируемые функции, встроенные непосредственно в интерпретатор. Стабильный пакет 5.40 включает, среди прочего:ceil,floor– целое округление безuse POSIX.trim– прокладка начального/конечного пробела из строки.indexed– соединяет каждый элемент со своим индексом; идиоматический компаньон с многозначнымforпетли (см. ниже).true,false,is_bool– набранные логические сентинели; сериализаторы теперь могут излучать JSONtrue/falseвместо1/0.weaken,unweaken,is_weak– контроль эталонного числа для построения двунаправленных структур данных без утечек памяти.blessed,reftype,refaddr– ссылка на интроспекцию.
Стабильное логическое отслеживание (5.36) – скаляры, созданные в виде булянов (например,
!!1) теперь сохраняют свой логический характер путем присвоения, что позволяет надежно производить сериализацию с учетом типа для JSON и MessagePack.Многозначный
forпетли (стабильный с 5.40; экспериментальный с 5.36) Итерация по парам или N-кортежам без ручного индекса арифметика:
use v5.40;
use builtin 'indexed';
for my ($i, $val) (indexed @scores) { ... } # index and value
Или захватить несколько значений одновременно
use v5.40;
for my ($val1, $val2, $val3) (@scores) { ... }
deferБлоки (экспериментальный с 5.36) – защитник, который безоговорочно выполняет код очистки при выходе блока, как обычно, так и через исключение, – естественная замена объектов защиты области на основе деструктора и важная модель управления ресурсами в конвейерах данных.
Перл 5.38 – июль 2023
PERL_RAND_SEEDпеременная окружения (5.38) – установка этой переменной перед выполнениемrandвызов (без явногоsrand) производят одну и ту же последовательность, позволяя воспроизводимым стохастическим алгоритмам – симуляциям, случайной выборке, методам Монте-Карло – без изменения исходного кода.class/field/methodсинтаксис (экспериментальный с 5.38) – специально созданная система объектов с лексическим диапазоном, не требующая ниblessнет@ISAни какого-либо модуля CPAN. Полезно для определения объектов с типизированными значениями, таких как строки набора данных, параметры модели или этапы конвейера:
use feature 'class';
no warnings 'experimental::class';
class Vector2D {
field $x :param;
field $y :param;
method magnitude { sqrt($x**2 + $y**2) }
}
my $v = Vector2D->new(x => 3, y => 4);
say $v->magnitude; # 5
Перл 5.40 – июнь 2024
try/catchобработка исключений (стабильная с 5.40; экспериментальная с 5.34;finallyблок добавлен в 5.36) – структурированная обработка исключений теперь является основной функцией языка; модуль CPAN не требуется:
use v5.40;
try {
my $result = load_and_process($file);
}
catch ($e) {
warn "Pipeline error: $e";
}
finally {
close_resources(); # runs whether or not an exception was thrown
}
(Try::Tiny / Feature::Compat::Try требуется только при нацеливании перлов старше 5,34.)
Многозначный
forпетли (стабильный с 5.40) – см. запись 5.36 выше; они окончили экспериментальный до стабильный в этом выпуске.builtin::infиbuiltin::nan(эксперимент с 5.40) – набранная бесконечность с плавающей точкой и константы без числа, исключающие9**9**9или POSIX хаки в числовом коде.^^логический оператор XOR (5.40) – завершает набор логических операторов со средним приоритетом (&&,||,^^); подходит для операций с логической маской.use v5.40импортирует встроенные функции – помимо включения пакета функций,use v5.40также импортирует соответствующиеbuiltinпакет версии, делая все стабилизированнымbuiltin::функции, доступные как краткие имена без отдельногоuse builtinзаявление.
Долгосрочные характеристики (до 5.36)
sayиstate(с 5.10) –sayявляетсяprintс неявной новой линией;stateобъявляет лексику, которая сохраняется при вызовах ее закрывающей подлодки (легкая примитивная память).
Ссылки и закрытия первого класса – анонимные подписки, закрытия и эталонная конструкция являются фундаментальными и стабильными с Perl 5.
use constantили CPANReadonlyмодуль для названных констант;Readonlyобеспечивает глубокую неизменность, котораяuse constantнет.
В сочетании с perlbrew или plenv для управления версиями и carton для воспроизводимых снимков зависимостей современный проект Perl выглядит и выглядит как первоклассная разработка программного обеспечения.
Честные ограничения
No case for Perl is complete without honesty about where it falls short:
Визуализация – Perl не имеет эквивалента ggplot2 или matplotlib. Для печати обычно требуется внешний вызов R, gnuplot или веб-библиотеки. Иногда эта слабость может стать реальной силой, позволяя использовать Perl5 в качестве языка приложения, который управляет и улучшает других участников.
Движение сообщества – сообщество науки о данных объединилось на Python и R. Найти готовые учебные пособия, ответы Stack Overflow и соавторов сложнее.
Ориентация объекта – без Moose/Moo модель OOP является вербальной; с ними она добавляет зависимость. Новый class функция может решить некоторые из этих проблем
Безопасность в масштабе – динамические скаляры основного языка затрудняют создание больших, объединенных числовых баз кода (см. следующий раздел).
2. Система типа данных Perl – сильные стороны и ограничения кэша-эры
Типы базовых перфораторов
Фундаментальная модель данных Перла основана на трех конструкциях:
| | | | | | | | | | | | | | | | |
|———–|——-|—————|
Скаляр | $ | Одно значение: число, строка, ссылка или undef |
| Массив | @ | упорядоченный список скаляров, индексированный целым числом |
| Хеш | % | Незаказанная коллекция скалярных значений с ключом по строке |
Все остальное – объекты, замки, сложные структуры данных – построено из этих трех примитивов с помощью ссылок (\@array, \%hash, sub { ... }).
Эта модель чрезвычайно гибкая. Один массив может содержать целые числа, числа с плавающей запятой, строки и вложенные ссылки одновременно. Именно эта гибкость сделала Перла доминирующим языком системного администрирования и веб-скриптов в течение двух десятилетий.
Проблема иерархии кэша
Современные ЦП достигают пиковой пропускной способности только при передаче данных через кэш L1/L2/L3† в больших, смежных блоках – свойство, называемое пространственной локальностью. Массивы Perl этого не предоставляют. Под капотом массив Perl представляет собой массив C из указателей для высокоразмещенного скаляра (SV) структуры. Каждый скаляр содержит количество ссылок, тег типа и заполнение – обычно 24–56 байт на скаляр в 64-битной сборке. Таким образом, итерация более миллиона элементов массива Perl включает в себя миллионы ссылок, разбросанных по куче, создавая шаблон пропуска кэша, который полностью отрицает преимущество скорости современных трубопроводов SIMD.
A concrete consequence: Точечный продукт из двух 1 000-элементных векторов, написанных в чистом перле, примерно 100-1000× медленнее, чем эквивалентная операция на паре плавающих ndarrays PDL, которые занимают две плоские, 4 000-байтовые области памяти, которые удобно помещаются в кэш L1.
Контраст с R
R занимает любопытную середину. Как и Перл, это динамический, интерпретируемый язык – переменные – это нетипированные контейнеры, функции – первоклассные значения, а интерактивный REPL – основная среда разработки. R даже имеет прямые аналоги с тремя основными типами Perl:
| Концепция Perl | аналог R |
|---|---|
$scalar |
длина-1 атомный вектор или скаляр-в-списке |
@array |
list() |
%hash |
имя |
Ссылка (\@arr) |
R не использует явные ссылки; вместо этого используется семантика копирования при изменении |
Но тип workhorse R, то есть атомный вектор, не имеет прямого аналога Perl. Атомный вектор R – это непрерывный, однородно типизированный блок памяти – именно тот макет, который вознаграждает кэш процессора. Каждый встроенный скаляр в R на самом деле является атомным вектором длины-1; нет “скалярный” вне атомных векторов.
Этот выбор дизайна означает, что R-код естественным образом работает на векторах миллионов двойников с пропускной способностью уровня BLAS, без написания пользователем одного цикла или выделения специального “массив” объект.
Атомные типы R:
| Атомный тип | Хранение | С эквивалент |
|---|---|---|
logical |
4 байт/элемент | int (с НС) |
integer |
4 байт/элемент | int32_t |
double |
8 байт/элемент | double |
complex |
16 байт/элемент | _Complex double |
character |
указатель на CHARSXP | char * (внутренний) |
raw |
1 байт/элемент | uint8_t |
R also defines higher-level structures built on atomic vectors:
- матрица – 2D атомный вектор с
dimатрибут. - массив – атомный вектор N-D с
dimатрибут. - data.frame – именованный список атомных векторов равной длины; lingua franca of
табличные данные в R. - фактор – целочисленный вектор с
levelsатрибут; кодирует категорические данные.
The lesson: Вычислительная производительность R при использовании в приложениях статистики и науки о данных поступает непосредственно из его смежных атомных векторов. Эквивалентный путь Перла к производительности является расширением (который также является автономным) matlab например, окружающая среда), язык данных Perl PDL.
3. Введите PDL: сильно набитые N-мерные массивы
Perl Data Language (PDL, pdl.perl.org) расширяет Perl с ndarrays (N-мерные массивы): смежные, сильно типизированные буферы памяти, которые выглядят и чувствуют себя первоклассными объектами Perl.
use PDL;
# A 1-D float ndarray — 4 bytes × 5 elements in one contiguous block
my $v = float( 1.0, 2.0, 3.0, 4.0, 5.0 );
# A 128-dimensional random database of 1000 vectors — all in cache-friendly memory
my $db = random( 128, 1000 ); # double by default
# Dot product of every DB vector against a query — a single BLAS call
my $scores = $db x $query->transpose;
примитивных типов PDL
PDL exposes the full palette of C numeric types as first-class constructors:
| Тип PDL | Байты | Тип C | Конструктор |
|---|---|---|---|
byte |
1 | uint8_t |
byte(...) |
short |
2 | int16_t |
short(...) |
ushort |
2 | uint16_t |
ushort(...) |
long |
4 | int32_t |
long(...) |
indx 4 или 8 |
ssize_t |
indx(...) |
|
longlong |
8 | int64_t |
longlong(...) |
float |
4 | float |
float(...) |
double |
8 | double |
double(...) |
cfloat |
8 | _Complex float |
cfloat(...) |
cdouble |
16 | _Complex double |
Резьба и SIMD
Одной из наиболее отличительных особенностей PDL является *неявная цепочка *: операции автоматически передаются по дополнительным измерениям, устраняя явные циклы в пользовательском коде и делегируя внутренние циклы оптимизированным ядрам C или Fortran. В сочетании с set_autopthread_targ(N), PDL автоматически распараллеливает независимые срезы поперек N Потоки ОС – без записи пользователем одного fork или Thread::Queue звоните.
Недопустимые значения
PDL имеет встроенную концепцию плохих значений (PDL::Bad), непосредственно аналогично R NA. ndarray может быть помечен как “осведомленность о плохом значении”, и операции PDL правильно распространяют дефекты с помощью арифметики, статистики и ввода-вывода.
4. Сравнение типов: Perl, PDL и R бок о бок
В таблице ниже каждый часто используемый тип R сопоставляется с его ближайшими аналогами Perl и PDL, выделяя, где три языка согласуются, различаются или дополняют друг друга.
| |
|—|—|—|—|
Основные выводы
Для чистых числовых, однородных данных (векторов, матриц, тензоров), PDL ndarrays и R атомные векторы функционально эквивалентны и сопоставимо эффективны.
Для гетерогенных табличных данных (смешанные типы, строковые столбцы, факторы), R’s
data.frameявляется более эргономичным; Perl обычно использует хэш массивов или выделенный модуль, такой какData::FrameилиPDL::IO::CSV.Для текста, неправильных структур и системного клея, родные типы Перла превосходят как R, так и Python.
Таким образом, комбинация Perl+PDL обеспечивает союз того, что R предлагает в качестве статистического языка, и того, что Perl предлагает в качестве системного языка – за счет более крутой кривой обучения и менее готового, откровенно ограниченного статистического инструментария.
Однако комбинация Perl+PDL+R (при этом последний используется как компонент, или инструментализированный через Perl)
5. Дорожная карта: что покрывает остальная часть этой серии
В этой серии описывается создание векторного ядра базы данных, встроенного в Perl5 + PDL с нуля. Векторные базы данных лежат в основе современных конвейеров генерации с расширенным извлечением (RAG), семантического поиска и систем рекомендаций ближайших соседей. Внедрение одного из первых принципов является отличным средством для демонстрации численных возможностей PDL наряду с сильными сторонами системного программирования Perl.
Каталог, совместно разработанный вместе с этими сообщениями, содержит следующие компоненты, каждый из которых будет предметом одного или нескольких выделенных сообщений, которые будут ссылаться на файлы в выделенном репозитории.
Post 1 – Сериализация и ввод-вывод: VectorIO модуль
Файл: VectorIO.pm
Двигатель хранит векторы как упакованные бинарные blobs внутри MessagePack полезные нагрузки. Эта статья охватывает:
- Проектирование модуля с чистым
Exporterна основе общедоступного API подuse v5.40. - Помощники проверки, обеспечивающие правильность схемы на границах системы.
Post 2 – Моделирование векторной базы данных
Файл: simulate_vectorDB.pl
Прежде чем искать базу данных, нужно ее выполнить. Это сообщение показывает:
- Создание воспроизводимых случайных поплавковых векторов с
PDL::random. - Использование
GetOpt::Longдля эргономичного анализа параметров CLI. - Написание a
--seed-контролируемое моделирование, которое создает идентичные базы данных в разных прогонах, что важно для бенчмаркинга.
Post 3 – Бенчмаркинг: The timing_DB Модуль
Файл: timing_DB.pm
Требования к исполнению требуют измерения. Это сообщение представляет:
- Многоразовый бенчмаркинг Perl, построенный на
Time::HiRes. - Методология сравнения справедливых настенных часов между реализациями Perl/PDL и R.
- Интерпретация пропускной способности (векторы/секунды) в сравнении с задержкой (мс/запрос) для различных размеров рабочих нагрузок.
Post 4 – Кластеризация K-средств с PDL::Stats::Kmeans
Файл: kmeans.pl
Кластеризация K-средних – это основа подхода инвертированного индекса (IVF) к приближенному поиску ближайших соседей. Эта статья охватывает:
- The
PDL::Stats::Kmeansинтерфейс и договор возврата (centroid,cluster,n,R2,ss). - Перевод
[obs × clusters]маска членства возвращенаrun_kmeans. - Сравнение центроидов Perl / PDL k-средних с R
kmeans()иClusterR::MiniBatchKmeans()для проверки числовой корректности.
Post 5 – Мини-пакетные K-средства: масштабирование до больших наборов данных
Файл: compare_kmeans_centroids.pl
Для полного k-средства требуются все данные в памяти для каждой итерации. Mini-batch k-means торгует небольшим количеством центроидной точности для значительного сокращения объема памяти и вычислительных ресурсов. Этот пост исследует:
- Реализация подлинной повторно подобранной мини-пакетной петли в PDL.
- Количественный дрейф центроидов между полным и мини-пакетным вариантами.
- Боковой выход с R
MiniBatchKmeansизClusterRпакет.
Post 6 – Поиск инвертированных файлов (IVF)
Файл: compare_ivf_search.pl
С центроидами в руке мы можем разбить базу данных и выполнить подлинейный приблизительный поиск ближайшего соседа. Эта статья охватывает:
- Building the inverted lists: отображение каждого вектора базы данных на ближайший центроид.
- The
unpack_inverted_listsпомощник вVectorIO. - Querying: поиск ближайших центроидов верхнего K, затем поиск только этих списков.
- Сравнение точности и скорости, поскольку количество проверенных списков варьируется.
Post 7 – Проверка против R: Численная правильность и межязыковые трубопроводы
Файлы: compare_kmeans_centroids.R, compare_kmeans_centroids_pure.R, plot_centroid_coordinates.R
The final post in the foundation series closes the loop between Perl and R:
- Экспорт результатов PDL в CSV и чтение их в R для независимой проверки.
- Использование ggplot2 для визуализации координат центроидов с обоих языков одновременно.
- шаблон рабочего процесса для “вычисления в Perl, визуализация в R” которые используют сильные стороны обеих экосистем.
Далее – Сообщение 1: Сериализация и ввод-вывод с
VectorIO.pm
† Современные ЦП имеют несколько уровней быстрой встроенной памяти, называемой кэшами (L1, L2, L3), которые находятся между ядрами процессора и основной оперативной памятью. L1 является самым маленьким (обычно 32–64 КБ на ядро) и самым быстрым (задержка 1–4 часовых циклов); L2 больше (256 КБ–1 МБ) и немного медленнее; L3 делится между ядрами (4–64 МБ) с более высокой задержкой. Основная оперативная память находится дальше с задержкой 60–100 нс – примерно в 200 раз медленнее, чем L1.
Когда вычисление затрагивает память в предсказуемом, непрерывном шаблоне, аппаратная программа prefetcher может загрузить предстоящие данные в L1/L2 до того, как это потребуется, обеспечивая почти пиковую пропускную способность. Разрозненная погоня за указателями (например, обход массива Perl с распределенными по куче скалярами) побеждает предварительную выборку, останавливая процессор, пока он ожидает, что каждый недостаток кэша будет разрешен из оперативной памяти.