Perl5 als Data Science-Sprache

[BEENDET] Zuletzt aktualisiert von am Fr., 17 Apr. 2026    Quelle
 

Data Science

Serieneinführung — Post 0 von N
Dieser Beitrag ist der erste in einer Serie, der die gemeinsame Entwicklung einer Vektordatenbank-Engine (VDBE) dokumentiert, die vollständig in Perl5 + PDL geschrieben wurde. Spätere Beiträge gehen durch jede Komponente dieses Motors; dieser stellt die Bühne. Der Hauptimpuls für diese Serie ist NICHT, dass Sie Ihren VDBE ablegen, da ich keine Leistungsansprüche mache, sondern zu zeigen, wie man Perl verwenden kann, um so ziemlich alles zu erreichen, was Sie mit jeder anderen Sprache erreichen können, aber intelligenter!


Inhaltsverzeichnis


1. Warum Perl5 für Data Science?

Wenn Data Scientists über Sprachentscheidungen sprechen, konvergiert das Gespräch schnell über Python, R oder Julia. Perl5 bekommt selten einen Platz am Tisch – aber es trägt eine überzeugende Reihe von Merkmalen, die einen zweiten Blick verdienen. Diese Eigenschaften haben sich im Laufe der Jahre nicht wesentlich verändert (Perl5 war schon immer so!), aber es sei denn, Sie wurden der Sprache ausgesetzt und haben gelernt, ihre Tercity, Rationalität, Flexibilität, Ausdrucksfähigkeit zu schätzen und sie tatsächlich verwendet, um Ihre Arbeit voranzutreiben, wissen Sie nicht, dass diese Funktionen nicht nur kostenlos mit Perl5 verfügbar sind, sondern Ihnen helfen können, Ihre Projekte voranzutreiben.

Allgegenwärtigkeit und Bereitstellung ohne Installation

Perl5 wird als Standardkomponente von praktisch jedem UNIX-ähnlichen Betriebssystem bereitgestellt: Linux-Distributionen, macOS, BSDs und viele eingebettete Linuxumgebungen umfassen eine funktionierende Perl binär out-of-the-box. Python hat hier Einzug gehalten, aber es ist immer noch üblich, Headless-Server, Netzwerk-Appliances oder HPC-Anmeldeknoten zu finden
wo Perl anwesend ist und ein voller Python-Stack nicht. Eine in Perl geschriebene Datenpipeline kann am ersten Tag ohne eine Conda Umwelt, a venvoder einem Container.

Portabilität vom Rechenzentrum bis zum Rand

Das gleiche Skript, das einen Terabyte-Datensatz auf einem 256-Core-HPC-Knoten analysiert, kann mit geringfügigen Konfigurationsänderungen auf einem Raspberry Pi, einem IoT-Gateway oder einem eingebetteten Controller ausgeführt werden. Perl’s Single-Binary Deployment-Modell und geringer Laufzeit-Overhead machen es zu einem echten “Einmal schreiben, überall laufen” Sprache in Umgebungen, in denen Python’Dolmetscher Overhead oder Julia’JIT-Aufwärmzeit wäre inakzeptabel.

Wenn Sie planen, überall bereitzustellen, und everywhere Perl5 ist Ihre offensichtliche Wahl.

Ein Erbe, das auf Text- und Datenmung basiert

Perl wurde von Grund auf für Textverarbeitung, reguläre Ausdrücke und “Kleber” zwischen Systemkomponenten arbeiten In der Praxis werden wissenschaftliche Datenpipelines nicht von numerischer Berechnung dominiert, sondern von Data Wrangling: Lesen heterogener Dateiformate, Bereinigen chaotischer Datensätze, Verknüpfen von Datensätzen aus verschiedenen Quellen und Weiterleiten von Ergebnissen zu nachgelagerten konsumierenden Komponenten.

Perl’Die Regex-Engine bleibt eine der leistungsstärksten verfügbaren, und Einzeiler können Datenreinigungsaufgaben ausführen, die Helferbibliotheken in anderen Sprachen erfordern würden.

Wenn Sie sich im Bereich des wissenschaftlichen Rechnens befinden, sind Sie möglicherweise auf die Begriffe workflow management systems und reproduzierbare Forschung gestoßen. Beide verlassen sich auf die Ausführung von End-to-End-Datentransformationen und Workflows, um die manuellen, fehleranfälligen und langwierigen Point-and-Click-Aktivitäten zu eliminieren, die Analysten und Wissenschaftler tun müssen, um ihre Daten in Erkenntnisse und Schlussfolgerungen zu verwandeln.

In dieser mutigen neuen Welt, Perl5’Die umfangreiche Historie ermöglicht es, sowohl als Komponente von Workflows als auch als Anwendungssprache, die diese Workflows implementiert, zu glänzen.

CPAN: ein kampferprobtes Modul-Ökosystem

Das Comprehensive Perl Archive Network (CPAN) hostet über 200.000 Module über alle erdenklichen Domänen hinweg. Während die Data-Science-Angebote nicht annähernd so umfangreich sind wie Python, sind die grundlegenden Komponenten für dedizierte Bauherren da.:

Modern Perl ist nicht Ihr Großvater’s Perl

Die folgenden Funktionen stammen direkt aus den offiziellen Versionshinweisen (perl5360delta, perl5380delta, perl5400delta) und organisiert durch das Release, in dem sie den Status stabil erreichten oder erstmals eingeführt wurden. Es werden nur Funktionen hervorgehoben, die für Data-Science- und Scientific-Computing-Workloads relevant sind.

5.36. Perl — Mai 2022

  use v5.36;
  sub clamp ($val, $lo = 0, $hi //= 1) {
      $val < $lo ? $lo : $val > $hi ? $hi : $val;
  }
  use v5.40;
  use builtin 'indexed';

for my ($i, $val) (indexed @scores)  { ... } # index and value

Oder mehrere Werte gleichzeitig abrufen

  use v5.40;

for my ($val1, $val2, $val3) (@scores)  { ... }

5.38. Perl — Juli 2023

  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

Perl 5.40 — Juni 2024

  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
  }

(Versuch::Tiny / Feature::Kompatibilität::Versuchen werden nur benötigt, wenn Perls älter als 5,34 sind.)

Langjährige Eigenschaften (vor 5.36)

Kombiniert mit Perlbrew oder Plenv für Versionsverwaltung und Karton Für reproduzierbare Abhängigkeits-Snapshots sieht ein modernes Perl-Projekt aus und fühlt sich an wie ein erstklassiger Software-Engineering-Aufwand.

Ehrliche Einschränkungen

Kein Fall für Perl ist komplett ohne Ehrlichkeit darüber, wo es zu kurz kommt:


2. Das Perl-Datentypsystem – Stärken und Cache-Era-Grenzen

Core-Perlentypen

Perl’Das grundlegende Datenmodell konzentriert sich auf drei Konstrukte:

Konstruieren Sigil Was es hält
Skalar $ Ein einzelner Wert: Zahl, Zeichenfolge, Referenz oder undef
Array @ Eine geordnete Liste von Skalaren, indexiert nach Ganzzahl
Hash % Eine ungeordnete Sammlung skalarer Werte, die nach Zeichenfolge eingegeben sind

Alles andere – Objekte, Verschlüsse, komplexe Datenstrukturen – wird aus diesen drei Primitiven über Referenzen (\@array, \%Hash, Untergeordnet { ... }).

Dieses Modell ist außerordentlich flexibel. Ein einzelnes Array kann Ganzzahlen, Gleitkommazahlen, Zeichenfolgen und verschachtelte Referenzen gleichzeitig enthalten. Genau diese Flexibilität machte Perl seit zwei Jahrzehnten zur dominierenden Systemverwaltungs- und Web-Scripting-Sprache.

Das Problem der Cache-Hierarchie

Moderne CPUs erzielen nur Spitzendurchsatz, wenn Daten den Cache L1/L2/L3 durchlaufen in großen, zusammenhängenden Blöcken — eine Eigenschaft namens räumliche Lokalität. Perl-Arrays liefern das nicht. Unter der Haube ist ein Perl-Array ein C-Array von Zeigern zum Heap-allozierten Skalar (SV) Structs. Jedes Skalar enthält eine Referenzanzahl, ein Typ-Tag und Polsterung – in der Regel 24–56 Byte pro Skalar bei einem 64-Bit-Build. Die Iteration über ein Perl-Array mit mehreren Millionen Elementen umfasst daher eine Million Zeigerdereferenzen, die über den Heap verstreut sind und ein Cache-Miss-Muster erzeugen, das den Geschwindigkeitsvorteil moderner SIMD-Pipelines vollständig negiert.

Eine konkrete Konsequenz: Ein Punktprodukt von zwei 1.000-Element-Vektoren, die in reinem Perl geschrieben sind, ist etwa 100-1000× langsamer als der äquivalente Vorgang bei einem Paar PDL-Float-Ndarrays, die zwei flache 4.000-Byte-Speicherbereiche belegen, die bequem in den L1-Cache passen.

Kontrast mit R

R nimmt einen kuriosen Mittelweg ein. Wie Perl ist es eine dynamische, interpretierte Sprache – Variablen sind nicht typisierte Container, Funktionen sind erstklassige Werte und die interaktive REPL ist die primäre Entwicklungsumgebung. R hat sogar direkte Analogien zu Perl’s drei Kerntypen:

Perl concept R analog
$skalar Länge-1 Atomvektor oder Skalar-in-Liste
@array list()
%Hash benannt list()
Referenz (\@arr) R verwendet keine expliziten Referenzen; stattdessen Copy-on-Modify-Semantik

Aber R’s workhorse-Typ, d.h. der atomare Vektor hat kein einfaches Perl-Gegenstück. Ein R-Atomvektor ist ein zusammenhängender, homogen typisierter Speicherblock - genau das Layout, das ein CPU-Cache belohnt. Jeder eingebaute Skalar in R ist eigentlich ein Längen-1 Atomvektor; es gibt keine “Bare skalar” außerhalb von atomaren Vektoren.

Diese Designauswahl bedeutet, dass R-Code auf natürliche Weise auf Vektoren von Millionen von Doppelpunkten mit BLAS-Level-Durchsatz arbeitet, ohne dass der Benutzer eine Single Loop schreibt oder eine spezielle “Array” Objekt.

R’Die atomaren Typen sind:

R atomarer Typ Lagerung C gleichwertig
logisch 4 Byte/Element int (mit NA Sentinel)
Ganzzahl 4 Byte/Element int32_t
doppelt 8 Byte/Element doppelt
Komplex 16 Bytes/Element Komplexes Doppel
Zeichen Zeiger auf CHARSXP Zeichen* (interniert)
Rohdaten 1 Byte/Element uint8_t

R definiert auch übergeordnete Strukturen, die auf atomaren Vektoren basieren:

Die Lektion: R’Die Rechenleistung, wenn sie in statistischen und Data Science-Anwendungen verwendet wird, fließt direkt aus ihren zusammenhängenden atomaren Vektoren. Perl’s äquivalenter Pfad zur Leistung ist eine Erweiterung (die auch ein Stand allein Matlab Wie Umgebung), die Perl Data Language PDL.


3. PDL eingeben: Stark typisierte N-Dimensional-Arrays

Die Perl Data Language (PDL, pdl.perl.org) erweitert Perl mit ndarrays (N-dimensionale Arrays): zusammenhängende, stark typisierte Speicherpuffer, die wie erstklassige Perlobjekte aussehen und sich anfühlen.

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-Primitive Typen

PDL stellt die vollständige Palette der C-Zahlentypen als erstklassige Konstruktoren zur Verfügung:

PDL-Typ Bytes C-Typ Konstruktor
Byte 1 uint8_t Byte(...)
kurz 2 int16_t kurz(...)
Warenkorb 2 uint16_t ushort(...)
lang 4 int32_t lang(...)
Indx 4 oder 8 ssize_t indx(...)
lange 8 int64_t lange(...)
Float 4 Float float(...)
doppelt 8 doppelt doppel(...)
Schwanz 8 Komplexer Puffer cfloat(...)
cdouble 16 Komplexes Doppel cdouble(...)

Gewinde und SIMD

Einer von PDL’implizites Threading: Vorgänge, die automatisch über zusätzliche Dimensionen übertragen werden, wodurch explizite Schleifen im Benutzercode beseitigt und innere Schleifen an optimierte C- oder Fortran-Kerne delegiert werden. Kombiniert mit set_autopthread_targ(N)PDL parallelisiert unabhängige Bereiche automatisch über N BS-Threads – ohne dass der Benutzer eine einzelne Gabel oder Thread::Warteschlange anrufen.

Falsche Werte

PDL hat ein eingebautes Konzept von schlechten Werten (PDL::Schlecht), direkt analog zu R’s NA. Ein Ndarray kann als “Falscher Wert bewusst”und PDL-Vorgänge propagieren Badness korrekt durch Arithmetik, Statistiken und I/O.


4. Typvergleich: Perl, PDL und R Side-by-Side

In der folgenden Tabelle werden alle gängigen R-Typen den nächstgelegenen Perl- und PDL-Pendants zugeordnet, wobei hervorgehoben wird, wo die drei Sprachen zustimmen, sich unterscheiden oder sich ergänzen.

R Typ Perl Äquivalent PDL Äquivalent Hinweise
doppelt (Länge-1) $x = 3,14 (skalar) doppelt(3.14) — Form () R hat keinen nackten Skalar; alles ist ein Vektor
Ganzzahl (Länge-1) $n = 42 (skalar) lang(42)
logisch (Länge-1) $Flag = 1 / $Flag = 0 Byte(1) Perl nutzt Wahrheit; PDL verwendet 0/1 Byte
doppelt Vektor @arr = (1,1, 2,2, 3,3) doppelt (1,1, 2,2, 3,3) PDL: zusammenhängend; @arr: Zeiger-Array
Ganzzahl Vektor @arr = (1, 2, 3) lang(1, 2, 3)
logisch Vektor @flags = (1, 0, 1) Byte(1, 0, 1)
Komplex Vektor — (kein eingebaut) cdouble(...) Perlbedarf Mathematik::Komplex; PDL hat native Unterstützung
Zeichen Vektor @strs = ('ein','B') — (nicht numerisch) PDL funktioniert nur auf Zahlen
Rohdaten Vektor pack('K*', @bytes) Byte(...)
NA undef Falscher Wert in ndarray PDL-Fehlerwerte verbreiten sich wie R’s NA
NULL undef im Listenkontext
Liste @array oder Referenz \@array
benannt Liste %Hash oder \%Hash
Matrix (2-D) Array-of-Arrays @aoa 2-D-Darray pdl([[...],[...]]) PDL: column-major; R: column-major
Array (N-D) verschachtelte Referenzen N-D ndarray $x->Umformung(...)
data.frame %Hash von @arrays 2-D ndarray (numerische Cols) + Perl-Hash (gemischt) Keine einzelnen PDL-Typ-Karten genau
Faktor Hash Lookup-Tabelle + @indices lang Ndarray + Perl @levels Array
Umgebung %Hash oder Package-Namespace
Funktion / Verschluss Untergeordnet { ... } / Verschluss PDL PP definiert kompilierte Kerne
Objekt S3/S4 gesegnete Referenz + Methodenversand PDL-Objekt (gesegnet ndarray) PDL-Objekte sind erstklassige Perl-Objekte

Wichtige Erkenntnisse

die Kombination von Perl+PDL+R (mit der letzteren als Komponente verwendet wird, oder instrumentalisiert perl)


5. Road Map: Was der Rest dieser Serie abdeckt

Diese Serie dokumentiert den Aufbau einer Vektordatenbank-Engine, die in Perl5 + PDL von Grund auf neu erstellt wurde. Vektordatenbanken untermauern moderne Retrieval-Augmented Generation-(RAG-)Pipelines, semantische Suche und benachbarte Empfehlungssysteme. Die Umsetzung eines der ersten Prinzipien ist ein hervorragendes Instrument zur Demonstration von PDL.’s numerische Fähigkeiten neben Perl’Systemprogrammierungsstärken.

Das neben diesen Beiträgen gemeinsam entwickelte Verzeichnis enthält die folgenden Komponenten, von denen jeder Gegenstand eines oder mehrerer dedizierter Beiträge sein wird, die Dateien in einem dedizierten Repository referenzieren.

Post 1 — Serialisierung und E/A: die VectorIO Modul

Datei: VectorIO.pm

Die Engine speichert Vektoren als gepackte binäre Blobs im Inneren MessagePack Payloads. Dieser Beitrag deckt:

Post 2 – Simulieren einer Vektordatenbank

Datei: simulate_vectorDB.pl

Bevor wir eine Datenbank durchsuchen können, brauchen wir eine. Dieser Beitrag zeigt:

Post 3 — Benchmarking: die timing_DB Modul

Datei: timing_DB.pm

Leistungsansprüche erfordern eine Messung. Dieser Beitrag stellt vor:

Post 4 – K-Means-Clustering mit PDL::Statistiken::Kmeans

Datei: kmeans.pl

K-Means-Clustering ist das Rückgrat des Inverted-File-Index-(IVF-)Ansatzes für die ungefähre Suche in der nächsten Nachbarschaft. Dieser Beitrag deckt:

Post 5 – Mini-Batch-K-Means: Skalierung auf große Datasets

Datei: compare_kmeans_centroids.pl

Vollständige k-Means erfordern alle Daten im Speicher für jede Iteration. Mini-batch k-bedeutet eine kleine Menge an Zentroid-Genauigkeit für eine große Reduzierung von Speicher und Rechenleistung. Dieser Beitrag untersucht:

Post 6 – Invertierter Dateiindex (IVF) Suche

Datei: compare_ivf_search.pl

Mit Centroids in der Hand können wir die Datenbank partitionieren und eine sublineare Näherungssuche durchführen. Dieser Beitrag deckt:

Post 7 — Validierung anhand von R: Numerische Korrektheit und sprachübergreifende Pipelines

Dateien: compare_kmeans_centroids.R, compare_kmeans_centroids_pure.R, plot_centroid_coordinates.R

Der letzte Beitrag in der Foundation-Serie schließt die Schleife zwischen Perl und R:


Weiter oben — Post 1: Serialisierung und E/A mit VectorIO.pm


Moderne CPUs haben mehrere Ebenen von schnellem On-Chip-Speicher namens caches (L1, L2, L3), die zwischen den Prozessorkernen und dem Haupt-RAM sitzen. L1 ist die kleinste (in der Regel 32–64 KB pro Core) und die schnellste (Latenzzeit bei 1–4-Uhrzyklen); L2 ist größer (256 KB–1 MB) und etwas langsamer; L3 wird über Kerne hinweg gemeinsam verwendet (4–64 MB) mit noch höherer Latenzzeit. Main RAM sitzt weiter entfernt bei 60-100 ns Latenz — etwa 200× langsamer als L1.

Wenn eine Berechnung Speicher in einem vorhersehbaren, zusammenhängenden Muster berührt, kann die Hardware prefetcher bevorstehende Daten in L1/L2 laden, bevor sie benötigt wird. Dadurch wird ein nahezu hoher Durchsatz erreicht. Verstreutes Pointer-Chasing (z. B. das Durchlaufen eines Perl-Arrays von Heap-zugewiesenen Skalaren) schlägt Prefetching aus und stoppt die CPU, während sie wartet, bis jeder Cache-Fehlschlag aus dem RAM aufgelöst wird.