perf(icmp): 实现自适应等待算法优化存活检测性能

- 新增 waitAdaptive 函数,监控响应增量实现智能提前结束
- 算法保守原则:最小等待1s + 连续500ms无新响应才提前结束
- 添加100ms检查间隔避免CPU空转
- 保留原有最大等待时间(3s/6s)作为兜底
- 添加完整单元测试覆盖各种场景

优化效果:
- 全部响应:~100ms (原3s)
- 无响应:~1s (原3s)
- 部分响应后稳定:~1.5s (原3s)
This commit is contained in:
ZacharyZcR
2026-01-21 18:35:44 +08:00
parent 8312808769
commit 5ecd3cfe4d
2 changed files with 188 additions and 24 deletions

View File

@@ -202,6 +202,77 @@ func printAliveStats(aliveHosts []string, hostslist []string) {
}
}
// ICMP 自适应等待参数
const (
icmpCheckInterval = 100 * time.Millisecond // 检查间隔,避免 CPU 空转
icmpMinWaitTime = 1 * time.Second // 最小等待时间,确保基础响应收集
icmpStableThreshold = 500 * time.Millisecond // 无新响应稳定阈值,超过此时间无新响应则提前结束
)
// waitAdaptive 自适应等待 ICMP 响应
// 算法:监控响应增量,连续一段时间无新响应则提前结束
// 保守原则:
// - 必须等待最小时间 (1s),确保基础响应收集
// - 只有"连续 500ms 无新响应"才提前结束
// - 保留原有最大等待时间作为兜底
func waitAdaptive(hostslist []string, aliveHosts *[]string, aliveHostsMu *sync.Mutex) {
totalHosts := len(hostslist)
// 根据主机数量设置最大超时时间(保持原有逻辑作为兜底)
maxWait := 6 * time.Second
if totalHosts <= 256 {
maxWait = 3 * time.Second
}
start := time.Now()
lastAliveCount := 0
lastChangeTime := start
for {
time.Sleep(icmpCheckInterval) // 避免 CPU 空转
// 读取当前存活数
aliveHostsMu.Lock()
aliveCount := len(*aliveHosts)
aliveHostsMu.Unlock()
elapsed := time.Since(start)
// 条件1所有主机都已响应立即结束
if aliveCount >= totalHosts {
common.LogDebug(fmt.Sprintf("[ICMP] 全部响应,耗时 %v", elapsed.Round(time.Millisecond)))
break
}
// 条件2超过最大等待时间兜底结束
if elapsed >= maxWait {
common.LogDebug(fmt.Sprintf("[ICMP] 达到最大等待时间 %v存活 %d/%d", maxWait, aliveCount, totalHosts))
break
}
// 条件3自适应提前结束
// 必须满足:已过最小等待时间 + 连续一段时间没有新响应
if elapsed >= icmpMinWaitTime {
if aliveCount > lastAliveCount {
// 有新响应,更新状态
lastChangeTime = time.Now()
lastAliveCount = aliveCount
} else if time.Since(lastChangeTime) >= icmpStableThreshold {
// 连续 500ms 没有新响应,认为响应已稳定,提前结束
common.LogDebug(fmt.Sprintf("[ICMP] 响应稳定,提前结束,耗时 %v存活 %d/%d",
elapsed.Round(time.Millisecond), aliveCount, totalHosts))
break
}
} else {
// 最小等待期内,持续更新状态
if aliveCount > lastAliveCount {
lastChangeTime = time.Now()
lastAliveCount = aliveCount
}
}
}
}
// RunIcmp1 使用ICMP批量探测主机存活(监听模式)
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string, aliveHosts *[]string, aliveHostsMu *sync.Mutex, config *common.Config, state *common.State, livewg *sync.WaitGroup) {
// 使用atomic.Bool保证并发安全
@@ -272,30 +343,10 @@ func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string,
_, _ = conn.WriteTo(IcmpByte, dst)
}
// 等待响应
start := time.Now()
for {
// 加锁读取aliveHosts长度
aliveHostsMu.Lock()
aliveCount := len(*aliveHosts)
aliveHostsMu.Unlock()
// 所有主机都已响应则退出
if aliveCount == len(hostslist) {
break
}
// 根据主机数量设置超时时间
since := time.Since(start)
wait := time.Second * 6
if len(hostslist) <= 256 {
wait = time.Second * 3
}
if since > wait {
break
}
}
// 自适应等待响应
// 算法:监控响应增量,连续一段时间无新响应则提前结束
// 保守原则:保留最大等待时间兜底,确保不漏掉慢响应主机
waitAdaptive(hostslist, aliveHosts, aliveHostsMu)
endflag.Store(true)
_ = conn.Close()

View File

@@ -2,7 +2,9 @@ package core
import (
"fmt"
"sync"
"testing"
"time"
)
// TestCheckSum 测试ICMP校验和计算RFC 1071算法
@@ -484,6 +486,117 @@ func TestMakemsg(t *testing.T) {
})
}
// TestWaitAdaptive 测试自适应等待算法
func TestWaitAdaptive(t *testing.T) {
t.Run("全部响应-立即结束", func(t *testing.T) {
hostslist := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"}
aliveHosts := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"} // 全部存活
var mu sync.Mutex
start := time.Now()
waitAdaptive(hostslist, &aliveHosts, &mu)
elapsed := time.Since(start)
// 全部响应应该在 1 个检查周期内结束 (~100ms)
if elapsed > 200*time.Millisecond {
t.Errorf("全部响应后应快速结束,实际耗时 %v", elapsed)
}
})
t.Run("无响应-自适应提前结束", func(t *testing.T) {
hostslist := make([]string, 10) // 10 个主机
for i := range hostslist {
hostslist[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
aliveHosts := []string{} // 无响应
var mu sync.Mutex
start := time.Now()
waitAdaptive(hostslist, &aliveHosts, &mu)
elapsed := time.Since(start)
// 无响应时lastChangeTime = start
// 在 minWait(1s) 后time.Since(lastChangeTime) >= 1s > stableThreshold(500ms)
// 所以会在约 1s 时提前结束(这是自适应优化的效果)
// 相比原来的固定 3s节省了约 2s
if elapsed < 900*time.Millisecond || elapsed > 1300*time.Millisecond {
t.Errorf("无响应时应在约 1s 提前结束,实际耗时 %v", elapsed)
}
})
t.Run("部分响应后稳定-提前结束", func(t *testing.T) {
hostslist := make([]string, 100)
for i := range hostslist {
hostslist[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
// 模拟 50% 响应
aliveHosts := make([]string, 50)
for i := range aliveHosts {
aliveHosts[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
var mu sync.Mutex
start := time.Now()
waitAdaptive(hostslist, &aliveHosts, &mu)
elapsed := time.Since(start)
// 响应已稳定(不再变化),应该在 minWait + stableThreshold 后结束
// 即约 1.5s,而不是 3s
if elapsed > 2*time.Second {
t.Errorf("响应稳定后应提前结束,实际耗时 %v", elapsed)
}
})
t.Run("持续响应-等待完成", func(t *testing.T) {
hostslist := make([]string, 10)
for i := range hostslist {
hostslist[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
aliveHosts := []string{}
var mu sync.Mutex
// 模拟持续响应:每 200ms 增加一个存活主机
done := make(chan struct{})
go func() {
defer close(done)
for i := 0; i < 10; i++ {
time.Sleep(200 * time.Millisecond)
mu.Lock()
aliveHosts = append(aliveHosts, fmt.Sprintf("192.168.1.%d", i+1))
mu.Unlock()
}
}()
start := time.Now()
waitAdaptive(hostslist, &aliveHosts, &mu)
elapsed := time.Since(start)
<-done // 等待 goroutine 结束
// 10 个主机 * 200ms = 2s全部响应后应立即结束
// 总耗时应该在 2s 左右
if elapsed < 1800*time.Millisecond || elapsed > 2500*time.Millisecond {
t.Errorf("持续响应时应等待全部完成,实际耗时 %v", elapsed)
}
})
}
// BenchmarkWaitAdaptive 基准测试自适应等待性能
func BenchmarkWaitAdaptive(b *testing.B) {
hostslist := make([]string, 100)
for i := range hostslist {
hostslist[i] = fmt.Sprintf("192.168.1.%d", i+1)
}
// 全部响应场景
aliveHosts := make([]string, 100)
copy(aliveHosts, hostslist)
var mu sync.Mutex
b.ResetTimer()
for i := 0; i < b.N; i++ {
waitAdaptive(hostslist, &aliveHosts, &mu)
}
}
// BenchmarkCheckSum 基准测试校验和性能
func BenchmarkCheckSum(b *testing.B) {
msg := make([]byte, 40)