Algorithm-Classifier-IsolationForest

 view release on metacpan or  search on metacpan

benchmarking/bench-streamd.pl  view on Meta::CPAN

#!/usr/bin/perl
# benchmarking/bench-streamd.pl
#
# Benchmarks `iforest streamd` end to end: the script spawns its own
# daemon on a temp Unix socket and pumps rows through the JSON-lines
# protocol, measuring points/second wall-clock at the client.
#
# Sections:
#   1. in-process baseline -- score_learn on an identical model in this
#      process; the ceiling everything else is measured against.  The
#      gap between it and the socket numbers is protocol + JSON + IPC
#      overhead, not model work.
#   2. batch-size sweep    -- prequential rows per {"rows": [...]}
#      message; directly informs streamc's --batch choice.
#   3. modes               -- prequential vs score vs learn at a fixed
#      batch size.
#   4. row forms           -- positional arrays vs tagged objects (the
#      tagged form pays hashref building + tagged_row_to_array).
#   5. concurrent clients  -- total throughput with 1/2/4 connections
#      pumping at once.  The daemon is a single select loop sharing one
#      model, so this should stay ~flat: it measures fairness overhead,
#      not parallel speedup.
#   6. command latency     -- ping round trips (pure protocol floor)
#      and the wall cost of an on-demand save.
#
# Reference numbers (2026-07-08, 8-core dev box, C backend,
# Cpanel::JSON::XS, 100 trees, window 2048, eta 32, 5 features):
# in-process score_learn ~2,770 pts/s; over the socket ~2,700 pts/s at
# any batch >= 16 (~2% overhead) and ~2,500 pts/s even at batch 1;
# score mode ~29,000 pts/s (no learning -- the tree walk is cheap, the
# learn is what costs); tagged objects ~21,000 vs positional ~30,000 in
# score mode; throughput is flat across 1/2/4 concurrent clients (one
# shared model, one loop -- by design); ping ~29,000 round trips/s;
# save ~140 ms.  The wire is nowhere near the bottleneck -- the model
# is.
#
# Run with:
#   perl -Ilib benchmarking/bench-streamd.pl

use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin";
use BenchAccel                                     qw(wall_rate wall_time_median);
use Time::HiRes                                    qw(time);
use File::Temp                                     qw(tempdir);
use IO::Select                                     ();
use IO::Socket::UNIX                               ();
use POSIX                                          ();
use Algorithm::Classifier::IsolationForest::Online ();

eval { require JSON::MaybeXS; 1 }
	or die "this benchmark needs JSON::MaybeXS (streamd's wire protocol): $@";
my $JSON = JSON::MaybeXS->new( utf8 => 1 );

use constant PI => 3.14159265358979;

sub gaussian {
	my ( $mu, $sigma ) = @_;
	return $mu + $sigma * sqrt( -2 * log( rand() || 1e-12 ) ) * cos( 2 * PI * rand() );
}

sub make_data {
	my ( $n, $nf ) = @_;
	return [
		map {
			[ map { gaussian( 0, 1 ) } 1 .. $nf ]
		} 1 .. $n
	];
}

# --- model shape (matches bench-online-score-accel.pl) -------------------
my $N_TREES = 100;
my $WINDOW  = 2048;
my $ETA     = 32;
my $NF      = 5;
my @TAGS    = map { "f$_" } 0 .. $NF - 1;

# --- spawn the daemon -----------------------------------------------------
my $bin = "$FindBin::Bin/../src_bin/iforest";
die "cannot find $bin\n" unless -f $bin;

my $tmp  = tempdir( CLEANUP => 1 );
my $sock = "$tmp/b.sock";
die "temp socket path too long for a Unix socket ($sock)\n" if length($sock) > 100;



( run in 0.946 second using v1.01-cache-2.11-cpan-6aa56a78535 )