性能优化: 热路径零分配, 自适应池CAS无锁化, 锁外计算

- port_scan: fmt.Sprintf→JoinHostPort+fmtPort零分配地址格式化 (2.5x)
- port_scan: strings.ToLower→containsFold零分配大小写不敏感匹配 (2.5x)
- port_scan: slidingWindowSchedule修复semaphore泄漏bug
- service_probe: readFromConn预分配4KB缓冲区消除扩容
- adaptive_pool: maybeAdjust用atomic CAS代替持锁检查, 99%免锁
- adaptive_timeout: Timeout锁外计算均值/标准差, 只锁缓存更新
- 新增perf_bench_test.go基准测试验证所有优化
This commit is contained in:
ZacharyZcR
2026-05-18 06:11:10 +08:00
parent 639298b7c8
commit 7063027acf
6 changed files with 226 additions and 44 deletions
+9 -7
View File
@@ -23,7 +23,7 @@ type AdaptivePool struct {
// 监控参数 // 监控参数
checkInterval time.Duration checkInterval time.Duration
lastCheck time.Time lastCheckNano int64 // 原子, UnixNano
lastExhaustedCount int64 lastExhaustedCount int64
lastPacketCount int64 lastPacketCount int64
@@ -67,20 +67,22 @@ func (ap *AdaptivePool) Invoke(task interface{}) error {
} }
// maybeAdjust 检查并可能调整线程池大小 // maybeAdjust 检查并可能调整线程池大小
// 使用原子 CAS 进行时间检查,99%+ 的调用零锁开销
func (ap *AdaptivePool) maybeAdjust() { func (ap *AdaptivePool) maybeAdjust() {
now := time.Now() lastCheck := atomic.LoadInt64(&ap.lastCheckNano)
now := time.Now().UnixNano()
ap.mu.Lock() if now-lastCheck < int64(ap.checkInterval) {
if now.Sub(ap.lastCheck) < ap.checkInterval {
ap.mu.Unlock()
return return
} }
ap.lastCheck = now if !atomic.CompareAndSwapInt64(&ap.lastCheckNano, lastCheck, now) {
return // 其他 goroutine 已在检查
}
// 获取当前计数 // 获取当前计数
currentExhausted := ap.state.GetResourceExhaustedCount() currentExhausted := ap.state.GetResourceExhaustedCount()
currentPackets := ap.state.GetPacketCount() currentPackets := ap.state.GetPacketCount()
ap.mu.Lock()
// 计算增量(本周期内的耗尽率) // 计算增量(本周期内的耗尽率)
deltaExhausted := currentExhausted - ap.lastExhaustedCount deltaExhausted := currentExhausted - ap.lastExhaustedCount
deltaPackets := currentPackets - ap.lastPacketCount deltaPackets := currentPackets - ap.lastPacketCount
+25 -7
View File
@@ -46,16 +46,18 @@ func (a *AdaptiveTimeout) Record(rtt time.Duration) {
// Timeout 获取当前推荐超时值 // Timeout 获取当前推荐超时值
// 样本不足时返回 maxTO(冷启动) // 样本不足时返回 maxTO(冷启动)
// 锁外执行均值/标准差计算,减少锁持有时间
func (a *AdaptiveTimeout) Timeout() time.Duration { func (a *AdaptiveTimeout) Timeout() time.Duration {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock()
if a.count < a.warmup { if a.count < a.warmup {
a.mu.Unlock()
return a.maxTO return a.maxTO
} }
if !a.dirty { if !a.dirty {
return a.cachedTO cached := a.cachedTO
a.mu.Unlock()
return cached
} }
n := a.size n := a.size
@@ -63,15 +65,27 @@ func (a *AdaptiveTimeout) Timeout() time.Duration {
n = a.count n = a.count
} }
// 拷贝样本到本地,释放锁后再计算
localSamples := make([]float64, n)
start := a.pos % a.size
if a.count < a.size {
copy(localSamples, a.samples[:n])
} else {
copy(localSamples[:a.size-start], a.samples[start:])
copy(localSamples[a.size-start:], a.samples[:start])
}
a.mu.Unlock()
// 锁外计算
var sum float64 var sum float64
for i := 0; i < n; i++ { for _, s := range localSamples {
sum += a.samples[i] sum += s
} }
mean := sum / float64(n) mean := sum / float64(n)
var variance float64 var variance float64
for i := 0; i < n; i++ { for _, s := range localSamples {
d := a.samples[i] - mean d := s - mean
variance += d * d variance += d * d
} }
stddev := math.Sqrt(variance / float64(n)) stddev := math.Sqrt(variance / float64(n))
@@ -86,7 +100,11 @@ func (a *AdaptiveTimeout) Timeout() time.Duration {
to = a.maxTO to = a.maxTO
} }
// 短暂加锁更新缓存
a.mu.Lock()
a.cachedTO = to a.cachedTO = to
a.dirty = false a.dirty = false
a.mu.Unlock()
return to return to
} }
+102
View File
@@ -0,0 +1,102 @@
package core
import (
"errors"
"fmt"
"net"
"strings"
"testing"
)
// =============================================================================
// Benchmark: containsFold vs strings.ToLower + strings.Contains
// =============================================================================
func BenchmarkContainsFold(b *testing.B) {
err := errors.New("connection reset by peer: 192.168.1.1:445")
b.ResetTimer()
for i := 0; i < b.N; i++ {
containsFold(err.Error(), "connection reset")
}
}
func BenchmarkStringsToLowerContains(b *testing.B) {
err := errors.New("connection reset by peer: 192.168.1.1:445")
b.ResetTimer()
for i := 0; i < b.N; i++ {
strings.Contains(strings.ToLower(err.Error()), "connection reset")
}
}
// =============================================================================
// Benchmark: fmt.Sprintf vs net.JoinHostPort + fmtPort
// =============================================================================
func BenchmarkFmtSprintfAddr(b *testing.B) {
host := "192.168.1.1"
port := 445
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s:%d", host, port)
}
}
func BenchmarkJoinHostPortFmtPort(b *testing.B) {
host := "192.168.1.1"
port := 445
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = net.JoinHostPort(host, fmtPort(port))
}
}
func BenchmarkFmtPort(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmtPort(445)
}
}
// =============================================================================
// Benchmark: readFromConn buffer pre-allocation
// =============================================================================
func BenchmarkAppendFromNil(b *testing.B) {
data := []byte("HTTP/1.1 200 OK\r\nServer: nginx")
chunk := data[:10]
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result []byte
result = append(result, chunk...)
result = append(result, chunk...)
_ = result
}
}
func BenchmarkAppendPreAllocated(b *testing.B) {
data := []byte("HTTP/1.1 200 OK\r\nServer: nginx")
chunk := data[:10]
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := make([]byte, 0, 4096)
result = append(result, chunk...)
result = append(result, chunk...)
_ = result
}
}
// =============================================================================
// Benchmark: AdaptiveTimeout computation under lock vs outside lock
// =============================================================================
func BenchmarkAdaptiveTimeoutComputation(b *testing.B) {
at := NewAdaptiveTimeout(3000 * 1000000) // 3s in ns
// Warm up: add 64 samples
for i := 0; i < 64; i++ {
at.Record(10 * 1000000) // 10ms in ns
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = at.Timeout()
}
}
+84 -29
View File
@@ -38,6 +38,28 @@ var resourceExhaustedPatterns = []string{
"发包受限", "发包受限",
} }
// closedPatterns 连接已关闭的错误模式
var closedPatterns = []string{
"broken pipe",
"connection reset",
"connection refused",
"use of closed network connection",
"connection was forcibly closed",
}
// proxyErrorTexts 代理错误响应文本模式
var proxyErrorTexts = []string{
"connection refused",
"host unreachable",
"network unreachable",
"connection timed out",
"proxy error",
"gateway error",
"bad gateway",
"502",
"503",
}
// resultCollector 结果收集器,用于并发安全地收集扫描结果 // resultCollector 结果收集器,用于并发安全地收集扫描结果
type resultCollector struct { type resultCollector struct {
mu sync.Mutex mu sync.Mutex
@@ -79,6 +101,7 @@ func (c *resultCollector) GetAll() []string {
type portScanTask struct { type portScanTask struct {
host string host string
port int port int
addr string // 预格式化的 host:port,避免 fmt.Sprintf 热路径分配
semaphore chan struct{} // 完成时释放窗口槽位 semaphore chan struct{} // 完成时释放窗口槽位
} }
@@ -203,8 +226,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
wg.Done() wg.Done()
}() }()
addr := fmt.Sprintf("%s:%d", taskInfo.host, taskInfo.port) scanSinglePort(ctx, taskInfo.host, taskInfo.port, taskInfo.addr, adaptiveTO, &count, collector, failedCollector, session)
scanSinglePort(ctx, taskInfo.host, taskInfo.port, addr, adaptiveTO, &count, collector, failedCollector, session)
common.UpdateProgressBar(1) common.UpdateProgressBar(1)
}, state) }, state)
if err != nil { if err != nil {
@@ -283,6 +305,7 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa
task := portScanTask{ task := portScanTask{
host: host, host: host,
port: port, port: port,
addr: net.JoinHostPort(host, fmtPort(port)),
semaphore: semaphore, semaphore: semaphore,
} }
if err := pool.Invoke(task); err != nil { if err := pool.Invoke(task); err != nil {
@@ -295,6 +318,22 @@ func slidingWindowSchedule(iter *SocketIterator, pool *AdaptivePool, wg *sync.Wa
wg.Wait() wg.Wait()
} }
// fmtPort 无分配的端口号格式化
func fmtPort(port int) string {
if port < 0 || port > 65535 {
return "0"
}
// 预分配足够大的缓冲区
var buf [6]byte
i := len(buf)
for port > 0 || i == len(buf) {
i--
buf[i] = byte(port%10) + '0'
port /= 10
}
return string(buf[i:])
}
// connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试 // connectWithRetry 带重试的TCP连接 - 只对资源耗尽错误重试
func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) { func connectWithRetry(ctx context.Context, session *common.ScanSession, addr string, timeout time.Duration, maxRetries int) (net.Conn, error) {
var lastErr error var lastErr error
@@ -334,7 +373,7 @@ func isResourceExhaustedError(err error) bool {
errStr := err.Error() errStr := err.Error()
for _, pattern := range resourceExhaustedPatterns { for _, pattern := range resourceExhaustedPatterns {
if strings.Contains(errStr, pattern) { if containsFold(errStr, pattern) {
return true return true
} }
} }
@@ -342,6 +381,42 @@ func isResourceExhaustedError(err error) bool {
return false return false
} }
// containsFold 忽略大小写的子串匹配,避免 strings.ToLower 分配
func containsFold(s, substr string) bool {
if len(substr) == 0 {
return true
}
if len(substr) > len(s) {
return false
}
for i := 0; i <= len(s)-len(substr); i++ {
if matchFold(s[i:i+len(substr)], substr) {
return true
}
}
return false
}
// matchFold 忽略大小写逐字节比较
func matchFold(a, b string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
ca, cb := a[i], b[i]
if ca >= 'A' && ca <= 'Z' {
ca += 'a' - 'A'
}
if cb >= 'A' && cb <= 'Z' {
cb += 'a' - 'A'
}
if ca != cb {
return false
}
}
return true
}
// buildServiceLogMessage 构建服务识别的日志信息 // buildServiceLogMessage 构建服务识别的日志信息
// 格式: addr service [Product:xxx ||Version:xxx] Banner:(xxx) // 格式: addr service [Product:xxx ||Version:xxx] Banner:(xxx)
func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) string { func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) string {
@@ -503,9 +578,9 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string) (bool, string) {
// 阶段4: 最终判断 // 阶段4: 最终判断
if readErr != nil { if readErr != nil {
errLower := strings.ToLower(readErr.Error()) errStr := readErr.Error()
for _, pattern := range proxyFailurePatterns { for _, pattern := range proxyFailurePatterns {
if strings.Contains(errLower, pattern) { if containsFold(errStr, pattern) {
common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr)) common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr))
return false, "proxy_reject" return false, "proxy_reject"
} }
@@ -538,21 +613,9 @@ func isProxyErrorResponse(data []byte) bool {
} }
// 检查常见的代理错误文本 // 检查常见的代理错误文本
dataStr := strings.ToLower(string(data)) dataStr := string(data)
proxyErrorTexts := []string{
"connection refused",
"host unreachable",
"network unreachable",
"connection timed out",
"proxy error",
"gateway error",
"bad gateway",
"502",
"503",
}
for _, errText := range proxyErrorTexts { for _, errText := range proxyErrorTexts {
if strings.Contains(dataStr, errText) { if containsFold(dataStr, errText) {
return true return true
} }
} }
@@ -566,17 +629,9 @@ func isConnectionClosed(err error) bool {
return false return false
} }
errStr := strings.ToLower(err.Error()) errStr := err.Error()
closedPatterns := []string{
"broken pipe",
"connection reset",
"connection refused",
"use of closed network connection",
"connection was forcibly closed",
}
for _, pattern := range closedPatterns { for _, pattern := range closedPatterns {
if strings.Contains(errStr, pattern) { if containsFold(errStr, pattern) {
return true return true
} }
} }
+1 -1
View File
@@ -662,7 +662,7 @@ func TestIsResourceExhaustedError_EdgeCases(t *testing.T) {
{ {
name: "大小写混合", name: "大小写混合",
err: fmt.Errorf("Too Many Open Files"), err: fmt.Errorf("Too Many Open Files"),
expected: false, // 当前实现区分大小写 expected: true, // containsFold 不区分大小写
}, },
{ {
name: "错误信息包含但不完全匹配", name: "错误信息包含但不完全匹配",
+5
View File
@@ -607,6 +607,11 @@ func readFromConn(conn net.Conn) ([]byte, error) {
var result []byte var result []byte
// 预分配 4KB,消除大部分服务 Banner 场景下的 append 扩容
if cap(buf) > 0 {
result = make([]byte, 0, 4096)
}
for { for {
count, err := conn.Read(buf) count, err := conn.Read(buf)