File Coverage

blib/lib/Algorithm/Classifier/IsolationForest/App/Command/accel.pm
Criterion Covered Total %
statement 53 63 84.1
branch 16 40 40.0
condition 3 9 33.3
subroutine 7 9 77.7
pod 4 5 80.0
total 83 126 65.8


line stmt bran cond sub pod time code
1             package Algorithm::Classifier::IsolationForest::App::Command::accel;
2              
3 22     22   270075 use strict;
  22         39  
  22         762  
4 22     22   84 use warnings;
  22         40  
  22         1127  
5 22     22   19708 use Algorithm::Classifier::IsolationForest ();
  22         107  
  22         816  
6 22     22   142 use Algorithm::Classifier::IsolationForest::App -command;
  22         48  
  22         318  
7              
8 1     1 1 25279 sub opt_spec { () }
9              
10 0     0 1 0 sub abstract { 'Report which (if any) native acceleration backend is active' }
11              
12             sub description {
13 0     0 1 0 'Initialises Algorithm::Classifier::IsolationForest, fits
14             a tiny synthetic dataset to exercise the optional native code path, then
15             reports which acceleration (if any) is wired up:
16              
17             * Inline::C -- C scoring backend compiled at module load
18             * OpenMP -- parallel tree-walk across CPU cores (requires libgomp)
19             * SIMD -- vectorised oblique dot product in extended mode
20             (gated on OpenMP 4.0+; relies on `#pragma omp simd`)
21              
22             The detection happens automatically the first time the module is loaded.
23             When the distribution was installed with Inline::C available, a prebuilt
24             object compiled at `make` time is loaded directly; otherwise the backend
25             is compiled on first load and cached under _Inline/. If no backend is
26             active the module falls back to a pure-Perl implementation.
27              
28             Build flags are tunable via environment variables set before first load
29             (and, to pick what gets baked into the prebuilt object, before
30             `perl Makefile.PL`):
31              
32             * IF_ARCH= -- -march= (e.g. x86-64-v3, skylake, znver3)
33             * IF_NATIVE=1 -- shorthand for IF_ARCH=native; ignored if IF_ARCH is set
34             * IF_OPT=<-Olevel> -- override the default -O3
35             * IF_NO_OPENMP=1 -- serial C backend: no libgomp linkage at all
36             * IF_RUNTIME_BUILD=1 -- ignore the prebuilt object, compile at first load
37             * IF_NO_C=1 -- skip the C backend entirely
38              
39             See "NATIVE ACCELERATION" in perldoc Algorithm::Classifier::IsolationForest
40             for details and tradeoffs (in particular, why IF_NATIVE is not always a
41             safe default choice).
42             ';
43             } ## end sub description
44              
45 1     1 0 3 sub validate { 1 }
46              
47             sub execute {
48 1     1 1 6 my ( $self, $opt, $args ) = @_;
49              
50             # Tiny deterministic dataset. Fitting + scoring confirms the chosen
51             # backend is callable end-to-end, not merely that it compiled. We
52             # exercise both axis mode (covers score_all_xs's axis branch) and
53             # extended mode (covers the oblique branch -- where the
54             # `#pragma omp simd` reduction lives, so this is the only path
55             # SIMD actually matters for).
56 1         2 srand(1);
57 1         4 my @data = map { [ rand(), rand(), rand() ] } 1 .. 30;
  30         47  
58 1         4 push @data, [ 10, 10, 10 ], [ -10, -10, -10 ];
59              
60 1         9 my $axis = Algorithm::Classifier::IsolationForest->new(
61             n_trees => 10,
62             sample_size => 32,
63             seed => 1,
64             );
65 1         5 $axis->fit( \@data );
66 1         5 $axis->score_samples( [ [ 0.5, 0.5, 0.5 ] ] );
67              
68 1         17 my $ext = Algorithm::Classifier::IsolationForest->new(
69             n_trees => 10,
70             sample_size => 32,
71             seed => 1,
72             mode => 'extended',
73             );
74 1         4 $ext->fit( \@data );
75 1         5 $ext->score_samples( [ [ 0.5, 0.5, 0.5 ] ] );
76              
77 1 50       5 my $has_c = $Algorithm::Classifier::IsolationForest::HAS_C ? 1 : 0;
78 1 50       2 my $has_openmp = $Algorithm::Classifier::IsolationForest::HAS_OPENMP ? 1 : 0;
79 1 50       3 my $has_simd = $Algorithm::Classifier::IsolationForest::HAS_SIMD ? 1 : 0;
80              
81 1         3 my $c_source = $Algorithm::Classifier::IsolationForest::C_SOURCE;
82              
83             # Whether a prebuilt object was installed with the dist at all --
84             # independent of whether this process ended up using it.
85 1         2 my $prebuilt_installed = 0;
86             {
87 1         1 local $@;
  1         2  
88 1         2 my $bf = eval {
89 1         8 require Algorithm::Classifier::IsolationForest::BuildFlags;
90 1         5 Algorithm::Classifier::IsolationForest::BuildFlags::flags();
91             };
92 1 50 33     10 $prebuilt_installed = 1 if ref $bf eq 'HASH' && $bf->{prebuilt};
93             }
94              
95 1 50       4 my $source_desc
    50          
96             = $c_source eq 'prebuilt' ? 'prebuilt at install time'
97             : $c_source eq 'runtime' ? 'compiled at run time (cached under _Inline/)'
98             : 'none (pure-Perl fallback)';
99              
100 1         15 print "Algorithm::Classifier::IsolationForest acceleration status\n";
101 1 50       3 print " Inline::C : ", ( $has_c ? "available\n" : "not available\n" );
102 1 50       3 print " OpenMP : ", ( $has_openmp ? "available\n" : "not available\n" );
103 1 50       2 print " SIMD : ", ( $has_simd ? "available\n" : "not available\n" );
104 1         3 print " C object : $source_desc\n";
105 1 50 33     9 if ( $c_source ne 'prebuilt' && $prebuilt_installed ) {
106 0         0 my @why;
107 0 0       0 push @why, 'IF_RUNTIME_BUILD' if $ENV{IF_RUNTIME_BUILD};
108 0 0       0 push @why, 'IF_NO_C' if $ENV{IF_NO_C};
109 0         0 push @why, grep { defined $ENV{$_} } qw(IF_OPT IF_ARCH IF_NATIVE IF_NO_OPENMP);
  0         0  
110 0 0       0 print " (a prebuilt object is installed but was not used",
111             (
112             @why
113             ? '; runtime overrides active: ' . join( ', ', @why )
114             : '; it failed to load'
115             ),
116             ")\n";
117             } ## end if ( $c_source ne 'prebuilt' && $prebuilt_installed)
118 1 50       18 if ($has_c) {
119 1         5 printf " Build flags: %s\n", $Algorithm::Classifier::IsolationForest::OPT_LEVEL;
120             }
121 1         2 print "\n";
122              
123             # Build a one-line backend summary that lists every active feature
124             # and where the C object came from.
125 1         2 my @features;
126 1 50       3 push @features, 'OpenMP' if $has_openmp;
127 1 50       4 push @features, 'SIMD' if $has_simd;
128              
129 1 50       4 my $suffix
    50          
130             = $c_source eq 'prebuilt' ? ' -- prebuilt at install time'
131             : $c_source eq 'runtime' ? ' -- compiled at run time'
132             : '';
133              
134 1 50 33     9 if ( $has_c && @features ) {
    0          
135 1         22 printf "Active backend: Inline::C with %s%s\n", join( ' + ', @features ), $suffix;
136             } elsif ($has_c) {
137 0         0 print "Active backend: Inline::C (serial, scalar)$suffix\n";
138             } else {
139 0         0 print "Active backend: pure Perl (no native acceleration)\n";
140             }
141              
142 1         182 return 1;
143             } ## end sub execute
144              
145             return 1;