perf(network-guard): peer table as a hash keyed by peer (P2)
ober
6b7c0a9abf6172eaa01414cec5a14957a2a09ddf
--- a/jsecmon/network-guard.ss +++ b/jsecmon/network-guard.ss @@ -93,7 +93,7 @@ (def (make-network-guard policy . maybe-now) (unless (network-policy? policy) (error 'make-network-guard "expected network policy" policy)) - (make-network-guard-raw policy (make-mutex) '() 0 0 0 + (make-network-guard-raw policy (make-mutex) (make-hash-table) 0 0 0 (if (pair? maybe-now) (car maybe-now) wall-ms))) (def (with-guard-lock guard thunk) @@ -102,17 +102,18 @@ thunk (lambda () (mutex-release (network-guard-mutex guard))))) - (def (find-peer-row rows peer) - (let loop ((xs rows)) - (cond ((null? xs) #f) - ((string=? (vector-ref (car xs) 0) peer) (car xs)) - (else (loop (cdr xs)))))) - - (def (prune-peer-rows rows now window-ms) - (filter (lambda (row) - (or (> (vector-ref row 1) 0) - (< (- now (vector-ref row 2)) window-ms))) - rows)) + ;; Drop inactive rows whose rate window has expired. `sources` is a hash table + ;; keyed by peer, so per-peer lookup is O(1); pruning collects the stale keys + ;; first so we never remove during iteration. + (def (prune-peer-rows! sources now window-ms) + (let ((stale '())) + (hash-for-each + (lambda (peer row) + (unless (or (> (vector-ref row 1) 0) + (< (- now (vector-ref row 2)) window-ms)) + (set! stale (cons peer stale)))) + sources) + (for-each (lambda (peer) (hash-remove! sources peer)) stale))) (def (network-guard-admit! guard peer) (unless (and (string? peer) (not (string=? peer ""))) @@ -122,18 +123,20 @@ (lambda () (let* ((policy (network-guard-policy guard)) (now ((network-guard-now guard))) - (window (network-policy-rate-window-ms policy))) + (window (network-policy-rate-window-ms policy)) + (sources (network-guard-sources guard))) (when (or (= (network-guard-global-window-start guard) 0) (>= (- now (network-guard-global-window-start guard)) window)) (network-guard-global-window-start-set! guard now) (network-guard-global-attempts-set! guard 0)) - (let* ((rows (prune-peer-rows (network-guard-sources guard) now window)) - (row (find-peer-row rows peer))) - (network-guard-sources-set! guard rows) - (when (and (not row) (< (length rows) *max-peer-rows*)) + (let ((row (hash-get sources peer))) + ;; New peer: prune stale rows only when the table is at capacity, then + ;; add if there is room. Existing peers skip the O(n) prune entirely. + (when (and (not row) (>= (hash-length sources) *max-peer-rows*)) + (prune-peer-rows! sources now window)) + (when (and (not row) (< (hash-length sources) *max-peer-rows*)) (set! row (vector peer 0 now 0)) - (network-guard-sources-set! guard - (cons row (network-guard-sources guard)))) + (hash-put! sources peer row)) (network-guard-global-attempts-set! guard (+ 1 (network-guard-global-attempts guard))) (if (not row) @@ -162,7 +165,7 @@ (with-guard-lock guard (lambda () - (let ((row (find-peer-row (network-guard-sources guard) peer))) + (let ((row (hash-get (network-guard-sources guard) peer))) (when (and row (> (vector-ref row 1) 0)) (vector-set! row 1 (- (vector-ref row 1) 1)) (network-guard-global-active-set! @@ -175,7 +178,7 @@ (with-guard-lock guard (lambda () - (let ((row (find-peer-row (network-guard-sources guard) peer))) + (let ((row (hash-get (network-guard-sources guard) peer))) (if row (vector-ref row 1) 0))))) ;; The pool owns exactly worker-count threads and a bounded FIFO. Server