bench: fix PHP arrow functions (fn()=> requires 7.4+), PASS=20/20 on PV
- Replace fn()=> with traditional function() syntax in php_medium and php_large - 8 arrow function occurrences fixed across processWords/statsArr/caesar/lookupOps/nested array_map - Added diag_bugs.sh: diagnostic script for PHP pool exhaustion + ruby/medium cold anomaly - bench_full.sh RUNID=9250986: PASS=20 FAIL=0 (was PASS=18 FAIL=2) - ruby/medium cold=81549ms anomaly confirmed reproducible (3rd time), not a FAIL - Added doc/report-2026-05-20-bench-php-analysis.md
This commit is contained in:
@@ -788,8 +788,8 @@ function sieve($n){$s=array_fill(0,$n+1,true);$s[0]=$s[1]=false;for($i=2;$i*$i<=
|
||||
function sqrtSum($n){$s=0;for($i=1;$i<=$n;$i++)$s+=sqrt($i);return $s;}
|
||||
function bubbleSort($arr){$n=count($arr);for($i=0;$i<$n;$i++)for($j=0;$j<$n-$i-1;$j++)if($arr[$j]>$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;}
|
||||
function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;}
|
||||
function processWords($w){return array_values(array_map(fn($s)=>strrev(strtoupper($s)),array_filter($w,fn($s)=>strlen($s)>3)));}
|
||||
function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];}
|
||||
function processWords($w){$f=array_filter($w,function($s){return strlen($s)>3;});return array_values(array_map(function($s){return strrev(strtoupper($s));},$f));}
|
||||
function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(function($x)use($m){return($x-$m)**2;},$arr))/$n;return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];}
|
||||
|
||||
function main($event,$context){
|
||||
$t0=microtime(true);
|
||||
@@ -818,10 +818,10 @@ function trigSum($n){$s=0;for($i=0;$i<$n;$i++)$s+=sin($i)*cos($i);return $s;}
|
||||
function bubbleSort($arr){$n=count($arr);for($i=0;$i<$n;$i++)for($j=0;$j<$n-$i-1;$j++)if($arr[$j]>$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;}
|
||||
function insertionSort($arr){for($i=1;$i<count($arr);$i++){$k=$arr[$i];$j=$i-1;while($j>=0&&$arr[$j]>$k){$arr[$j+1]=$arr[$j];$j--;}$arr[$j+1]=$k;}return $arr;}
|
||||
function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;}
|
||||
function processWords($w){return array_values(array_map(fn($s)=>['original'=>$s,'upper'=>strtoupper($s),'reversed'=>strrev($s),'len'=>strlen($s),'vowels'=>preg_match_all('/[aeiou]/',$s)],array_filter($w,fn($s)=>strlen($s)>=3)));}
|
||||
function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'min'=>min($arr),'max'=>max($arr),'mean'=>round($m,4),'std'=>round(sqrt($v),4)];}
|
||||
function caesar($t,$sh){return preg_replace_callback('/[a-zA-Z]/',fn($m)=>chr((ord($m[0])-(ctype_lower($m[0])?97:65)+$sh)%26+(ctype_lower($m[0])?97:65)),$t);}
|
||||
function lookupOps($n){$sq=array_combine(range(0,199),array_map(fn($i)=>$i*$i,range(0,199)));$cu=array_combine(range(0,199),array_map(fn($i)=>$i**3,range(0,199)));$r=0;for($i=0;$i<$n;$i++)$r+=($sq[$i%200]??0)+($cu[$i%200]??0);return $r;}
|
||||
function processWords($w){$f=array_filter($w,function($s){return strlen($s)>=3;});return array_values(array_map(function($s){return['original'=>$s,'upper'=>strtoupper($s),'reversed'=>strrev($s),'len'=>strlen($s),'vowels'=>preg_match_all('/[aeiou]/',$s)];},$f));}
|
||||
function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(function($x)use($m){return($x-$m)**2;},$arr))/$n;return['n'=>$n,'min'=>min($arr),'max'=>max($arr),'mean'=>round($m,4),'std'=>round(sqrt($v),4)];}
|
||||
function caesar($t,$sh){return preg_replace_callback('/[a-zA-Z]/',function($m)use($sh){return chr((ord($m[0])-(ctype_lower($m[0])?97:65)+$sh)%26+(ctype_lower($m[0])?97:65));},$t);}
|
||||
function lookupOps($n){$sq=array_combine(range(0,199),array_map(function($i){return $i*$i;},range(0,199)));$cu=array_combine(range(0,199),array_map(function($i){return $i**3;},range(0,199)));$r=0;for($i=0;$i<$n;$i++)$r+=($sq[$i%200]??0)+($cu[$i%200]??0);return $r;}
|
||||
|
||||
function main($event,$context){
|
||||
$words=$GLOBALS['WORDS'];
|
||||
@@ -833,8 +833,8 @@ function main($event,$context){
|
||||
$h256=substr(hash('sha256','bench-large-php-2026'),0,16);
|
||||
$md5=substr(hash('md5','bench-large-php-2026'),0,16);
|
||||
$b64=base64_encode('hello from bench-php-large');
|
||||
$ma=array_map(fn($i)=>array_map(fn($j)=>$i+$j,range(0,3)),range(0,3));
|
||||
$mb=array_map(fn($i)=>array_map(fn($j)=>$i*$j+1,range(0,3)),range(0,3));
|
||||
$ma=array_map(function($i){return array_map(function($j)use($i){return $i+$j;},range(0,3));},range(0,3));
|
||||
$mb=array_map(function($i){return array_map(function($j)use($i){return $i*$j+1;},range(0,3));},range(0,3));
|
||||
$mc=matMul($ma,$mb);
|
||||
$pw=processWords($words);$pst=statsArr(array_slice($primes,0,50));
|
||||
$cipher=caesar('hello bench world',13);
|
||||
@@ -845,7 +845,7 @@ function main($event,$context){
|
||||
'sqrt'=>round($sq,2),'log'=>round($lg,2),'trig'=>round($tr,4),
|
||||
'primes'=>count($primes),'fib40'=>$f40,'fibs_sum'=>array_sum($fibs),
|
||||
'h256'=>$h256,'md5'=>$md5,'b64_len'=>strlen($b64),
|
||||
'mat_trace'=>array_sum(array_map(fn($i)=>$mc[$i][$i],range(0,3))),
|
||||
'mat_trace'=>array_sum(array_map(function($i)use($mc){return $mc[$i][$i];},range(0,3))),
|
||||
'words'=>count($pw),'stats'=>$pst,'cipher'=>$cipher,
|
||||
'lookup'=>$lookup,'csum'=>round($csum,6),'elapsed'=>round($elapsed,5)]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env bash
|
||||
# diag_bugs.sh — диагностика двух аномалий из bench_full run=9248232
|
||||
# BUG-1: php/medium + php/large COLD-TIMEOUT (гипотеза: arrow functions PHP 7.4+)
|
||||
# BUG-2: ruby/medium cold=80451ms (гипотеза: poolmgr eviction — проверяем воспроизводимость)
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
|
||||
source "${ROOT_DIR}/scripts/lib.sh"
|
||||
|
||||
SUB="bench@bench.local"
|
||||
RUNID="diag$(date +%s | tail -c 6)"
|
||||
TMP=$(mktemp -d)
|
||||
|
||||
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
|
||||
RESULTS_DIR="${ROOT_DIR}/test-results/bench-func"
|
||||
LOGFILE="${RESULTS_DIR}/diag_bugs_${TIMESTAMP}.log"
|
||||
mkdir -p "$RESULTS_DIR"
|
||||
|
||||
declare -a ALL_FNS=()
|
||||
PASS_CNT=0
|
||||
FAIL_CNT=0
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP" 2>/dev/null || true
|
||||
for fn in "${ALL_FNS[@]:-}"; do
|
||||
curl -s -X DELETE "${BASE}/functions/${fn}" \
|
||||
-H "X-Auth-Token: ${SUB}" >/dev/null 2>&1 || true
|
||||
done
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
log() { echo "$@" | tee -a "$LOGFILE"; }
|
||||
escape_code() { python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'; }
|
||||
get_status() {
|
||||
python3 -c "import json,sys
|
||||
try: d=json.load(sys.stdin); print(d.get('status',''))
|
||||
except: print('')" 2>/dev/null
|
||||
}
|
||||
get_field() {
|
||||
local f="$1"
|
||||
python3 -c "import json,sys
|
||||
try: d=json.load(sys.stdin); print(d.get('$f',''))
|
||||
except: print('')" 2>/dev/null
|
||||
}
|
||||
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
log " diag_bugs.sh run=${RUNID} $(date)"
|
||||
log " API: ${BASE} SUB: ${SUB}"
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Вспомогательная: загрузить функцию, сделать cold+hot invoke, вернуть результат
|
||||
# bench_one LABEL LANG CODEFILE [EXPECTED_STR]
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
bench_one() {
|
||||
local label="$1" lang="$2" codefile="$3" expected="${4:-}"
|
||||
local fn="bench-${label}-${RUNID}"
|
||||
ALL_FNS+=("$fn")
|
||||
|
||||
# Upload
|
||||
local code_esc t0 t1 R CODE BODY UPLOAD_MS
|
||||
code_esc=$(cat "$codefile" | escape_code)
|
||||
t0=$(date +%s%3N)
|
||||
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"${fn}\",\"language\":\"${lang}\",\"code\":${code_esc}}" 2>/dev/null)
|
||||
t1=$(date +%s%3N)
|
||||
CODE=$(echo "$R" | tail -1)
|
||||
BODY=$(echo "$R" | head -n -1)
|
||||
UPLOAD_MS=$((t1-t0))
|
||||
|
||||
if [ "$CODE" != "201" ]; then
|
||||
log " [${label}] UPLOAD FAIL HTTP=${CODE}: $(echo "$BODY" | head -c 200)"
|
||||
FAIL_CNT=$((FAIL_CNT+1))
|
||||
return 1
|
||||
fi
|
||||
log " [${label}] upload OK ${UPLOAD_MS}ms"
|
||||
|
||||
# Cold start (retry 20×5s)
|
||||
local tc0 COLD_MS=0 cold_ok=0 cold_body=""
|
||||
tc0=$(date +%s%3N)
|
||||
for i in $(seq 1 20); do
|
||||
local resp s raw
|
||||
resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null)
|
||||
s=$(echo "$resp" | get_status)
|
||||
raw=$(echo "$resp" | get_field "response_raw")
|
||||
if [ "$s" = "200" ]; then
|
||||
if [ -z "$expected" ] || echo "$raw" | grep -qF "$expected"; then
|
||||
local tc1; tc1=$(date +%s%3N)
|
||||
COLD_MS=$((tc1-tc0))
|
||||
cold_ok=1
|
||||
cold_body="$raw"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ $cold_ok -eq 0 ]; then
|
||||
log " [${label}] COLD TIMEOUT"
|
||||
FAIL_CNT=$((FAIL_CNT+1))
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Hot ×3
|
||||
local HOT_TOTAL=0 hot_ok=0
|
||||
for i in 1 2 3; do
|
||||
local th0 th1 resp s
|
||||
th0=$(date +%s%3N)
|
||||
resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null)
|
||||
th1=$(date +%s%3N)
|
||||
s=$(echo "$resp" | get_status)
|
||||
[ "$s" = "200" ] && { HOT_TOTAL=$((HOT_TOTAL+th1-th0)); hot_ok=$((hot_ok+1)); }
|
||||
done
|
||||
local HOT_AVG=0; [ $hot_ok -gt 0 ] && HOT_AVG=$((HOT_TOTAL/hot_ok))
|
||||
|
||||
log " [${label}] OK cold=${COLD_MS}ms hot_avg=${HOT_AVG}ms (${hot_ok}/3)"
|
||||
[ -n "$cold_body" ] && log " [${label}] body=$(echo "$cold_body" | head -c 120)"
|
||||
PASS_CNT=$((PASS_CNT+1))
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# BUG-1: PHP arrow functions
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
log ""
|
||||
log "┌─────────────────────────────────────────────────────────────────┐"
|
||||
log "│ BUG-1: PHP arrow functions (fn()=> требует PHP 7.4+) │"
|
||||
log "└─────────────────────────────────────────────────────────────────┘"
|
||||
|
||||
# Тест 1а: минимальный PHP с arrow function — поддерживается?
|
||||
cat > "${TMP}/php_arrow_min.php" << 'EOF'
|
||||
<?php
|
||||
function main($event, $context) {
|
||||
$nums = [1,2,3,4,5];
|
||||
$doubled = array_map(fn($x) => $x * 2, $nums);
|
||||
return "arrow-ok:" . implode(",", $doubled);
|
||||
}
|
||||
EOF
|
||||
log ""
|
||||
log " [php-arrow-min] Тест: arrow function fn(\$x)=>\$x*2 (PHP 7.4+)"
|
||||
bench_one "php-arrow-min" "php" "${TMP}/php_arrow_min.php" "arrow-ok"
|
||||
|
||||
# Тест 1б: PHP version info через phpinfo-like
|
||||
cat > "${TMP}/php_version.php" << 'EOF'
|
||||
<?php
|
||||
function main($event, $context) {
|
||||
return "php-version:" . PHP_VERSION . " php-major:" . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;
|
||||
}
|
||||
EOF
|
||||
log ""
|
||||
log " [php-version] Тест: получить версию PHP runtime"
|
||||
bench_one "php-version" "php" "${TMP}/php_version.php" "php-version"
|
||||
|
||||
# Тест 1в: PHP medium без arrow functions (обычные function)
|
||||
cat > "${TMP}/php_medium_noarrow.php" << 'EOF'
|
||||
<?php
|
||||
function fib($n){$a=0;$b=1;for($i=0;$i<$n;$i++)[$a,$b]=[$b,$a+$b];return $a;}
|
||||
function sieve($n){$s=array_fill(0,$n+1,true);$s[0]=$s[1]=false;for($i=2;$i*$i<=$n;$i++)if($s[$i])for($j=$i*$i;$j<=$n;$j+=$i)$s[$j]=false;return array_keys(array_filter($s));}
|
||||
function sqrtSum($n){$s=0;for($i=1;$i<=$n;$i++)$s+=sqrt($i);return $s;}
|
||||
function bubbleSort($arr){$n=count($arr);for($i=0;$i<$n;$i++)for($j=0;$j<$n-$i-1;$j++)if($arr[$j]>$arr[$j+1]){$t=$arr[$j];$arr[$j]=$arr[$j+1];$arr[$j+1]=$t;}return $arr;}
|
||||
function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;}
|
||||
function processWordsNoArrow($words){
|
||||
$out=[];
|
||||
foreach($words as $s){
|
||||
if(strlen($s)>3) $out[]=strrev(strtoupper($s));
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
function statsArrNoArrow($arr){
|
||||
$n=count($arr);$m=array_sum($arr)/$n;
|
||||
$v=0; foreach($arr as $x) $v+=($x-$m)*($x-$m); $v/=$n;
|
||||
return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];
|
||||
}
|
||||
|
||||
function main($event,$context){
|
||||
$t0=microtime(true);
|
||||
$words=["alpha","beta","gamma","delta","epsilon","zeta","eta","theta","iota","kappa","lambda","mu"];
|
||||
$f=fib(35);$primes=sieve(500);$s=sqrtSum(1000);
|
||||
$nums=range(50,1);$sorted=bubbleSort($nums);
|
||||
$a=[[1,2,3],[4,5,6]];$b=[[7,8],[9,10],[11,12]];$c=matMul($a,$b);
|
||||
$pw=processWordsNoArrow($words);$st=statsArrNoArrow($primes);
|
||||
$h=substr(hash('sha256','bench-medium-php'),0,16);
|
||||
$elapsed=microtime(true)-$t0;
|
||||
return json_encode(['tag'=>'bench-php-medium-noarrow-ok','fib35'=>$f,'primes'=>count($primes),
|
||||
'sqrt'=>round($s,1),'sorted0'=>$sorted[0],'mat_c00'=>$c[0][0],
|
||||
'words'=>count($pw),'stats'=>$st,'hash'=>$h,'elapsed'=>round($elapsed,5)]);
|
||||
}
|
||||
EOF
|
||||
log ""
|
||||
log " [php-medium-noarrow] PHP medium логика без arrow functions (foreach вместо fn()=>)"
|
||||
bench_one "php-med-noarr" "php" "${TMP}/php_medium_noarrow.php" "bench-php-medium-noarrow-ok"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# BUG-2: ruby/medium cold=80451ms — воспроизводимость
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
log ""
|
||||
log "┌─────────────────────────────────────────────────────────────────┐"
|
||||
log "│ BUG-2: ruby/medium cold=80451ms — воспроизводимость (×3) │"
|
||||
log "└─────────────────────────────────────────────────────────────────┘"
|
||||
|
||||
cat > "${TMP}/ruby_medium_diag.rb" << 'EOF'
|
||||
require 'json'
|
||||
require 'digest'
|
||||
|
||||
WORDS=%w[alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega]
|
||||
|
||||
def fib(n); a,b=0,1; n.times{a,b=b,a+b}; a; end
|
||||
def sieve(n)
|
||||
s=Array.new(n+1,true); s[0]=s[1]=false
|
||||
(2..Math.sqrt(n).to_i).each{|i|(i*i..n).step(i){|j|s[j]=false} if s[i]}
|
||||
(0..n).select{|i|s[i]}
|
||||
end
|
||||
def sqrt_sum(n); (1..n).sum{|i|Math.sqrt(i)}; end
|
||||
def bubble_sort(arr)
|
||||
arr=arr.dup; n=arr.length
|
||||
n.times{(n-1).times{|j|arr[j],arr[j+1]=arr[j+1],arr[j] if arr[j]>arr[j+1]}}
|
||||
arr
|
||||
end
|
||||
def mat_mul(a,b)
|
||||
ra,ca,cb=a.length,a[0].length,b[0].length
|
||||
Array.new(ra){|i|Array.new(cb){|j|(0...ca).sum{|k|a[i][k]*b[k][j]}}}
|
||||
end
|
||||
def process_words(w); w.select{|s|s.length>3}.map{|s|s.upcase.reverse}; end
|
||||
def get_stats(arr)
|
||||
n=arr.length; m=arr.sum.to_f/n; v=arr.sum{|x|(x-m)**2}/n
|
||||
{n:n, mean:m.round(4), std:Math.sqrt(v).round(4)}
|
||||
end
|
||||
|
||||
def handler(ctx)
|
||||
t0=Time.now
|
||||
f=fib(35); primes=sieve(500); s=sqrt_sum(1000)
|
||||
nums=(1..50).to_a.reverse; sorted=bubble_sort(nums)
|
||||
a=[[1,2,3],[4,5,6]]; b=[[7,8],[9,10],[11,12]]; c=mat_mul(a,b)
|
||||
pw=process_words(WORDS); st=get_stats(primes)
|
||||
h=Digest::SHA256.hexdigest('bench-medium-ruby')[0,16]
|
||||
elapsed=Time.now-t0
|
||||
{ statusCode: 200, body: JSON.generate({tag:"bench-ruby-medium-ok", fib35:f, primes:primes.length,
|
||||
sqrt:s.round(1), sorted0:sorted[0], mat_c00:c[0][0],
|
||||
words:pw.length, stats:st, hash:h, elapsed:elapsed.round(5)}) }
|
||||
end
|
||||
EOF
|
||||
|
||||
log ""
|
||||
log " ruby/medium × 3 независимых функции (разные имена → чистый cold start каждый раз)"
|
||||
for attempt in 1 2 3; do
|
||||
log ""
|
||||
log " --- Попытка ${attempt}/3 ---"
|
||||
bench_one "ruby-med-${attempt}" "ruby" "${TMP}/ruby_medium_diag.rb" "bench-ruby-medium-ok"
|
||||
done
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# ИТОГ
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
log ""
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
log " ИТОГ: PASS=${PASS_CNT} FAIL=${FAIL_CNT} TOTAL=$((PASS_CNT+FAIL_CNT))"
|
||||
log " Лог: ${LOGFILE}"
|
||||
log " Готово: $(date)"
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
Reference in New Issue
Block a user