1
0

enhanced_memory_pools_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. package utils
  2. import (
  3. "fmt"
  4. "runtime"
  5. "strings"
  6. "testing"
  7. )
  8. // TestEnhancedObjectPool tests the enhanced object pool functionality
  9. func TestEnhancedObjectPool(t *testing.T) {
  10. pool := NewEnhancedObjectPool(
  11. func() *strings.Builder { return &strings.Builder{} },
  12. func(sb *strings.Builder) { sb.Reset() },
  13. 10,
  14. )
  15. // Test Get/Put cycle
  16. sb1 := pool.Get()
  17. if sb1 == nil {
  18. t.Fatal("Pool returned nil object")
  19. }
  20. sb1.WriteString("test data")
  21. pool.Put(sb1)
  22. // Get another object (should be reused)
  23. sb2 := pool.Get()
  24. if sb2 == nil {
  25. t.Fatal("Pool returned nil after put")
  26. }
  27. // Should be reset
  28. if sb2.Len() != 0 {
  29. t.Error("Object not properly reset")
  30. }
  31. // Verify stats
  32. stats := pool.Stats()
  33. if stats.Reused == 0 {
  34. t.Error("Pool stats not tracking reuse")
  35. }
  36. }
  37. // TestStringBuilderPool tests the string builder pool
  38. func TestStringBuilderPool(t *testing.T) {
  39. pool := NewStringBuilderPool(1024, 10)
  40. // Test basic functionality
  41. sb := pool.Get()
  42. if sb == nil {
  43. t.Fatal("String builder pool returned nil")
  44. }
  45. sb.WriteString("test data")
  46. if sb.String() != "test data" {
  47. t.Error("String builder not working correctly")
  48. }
  49. pool.Put(sb)
  50. // Test reuse
  51. sb2 := pool.Get()
  52. if sb2.Len() != 0 {
  53. t.Error("String builder not reset properly")
  54. }
  55. pool.Put(sb2)
  56. }
  57. // TestByteSlicePool tests the byte slice pool
  58. func TestByteSlicePool(t *testing.T) {
  59. pool := NewByteSlicePool()
  60. // Test different sizes
  61. sizes := []int{64, 128, 256, 512, 1024}
  62. slices := make([][]byte, len(sizes))
  63. for i, size := range sizes {
  64. slice := pool.Get(size)
  65. if cap(slice) < size {
  66. t.Errorf("Slice capacity %d is less than requested size %d", cap(slice), size)
  67. }
  68. slices[i] = slice
  69. }
  70. // Return all slices
  71. for _, slice := range slices {
  72. pool.Put(slice)
  73. }
  74. // Test reuse
  75. for _, size := range sizes {
  76. slice := pool.Get(size)
  77. if cap(slice) < size {
  78. t.Errorf("Reused slice capacity %d is less than requested size %d", cap(slice), size)
  79. }
  80. pool.Put(slice)
  81. }
  82. }
  83. // TestMapPool tests the map pool
  84. func TestMapPool(t *testing.T) {
  85. pool := NewMapPool[string, int](10, 5)
  86. // Test Get/Put cycle
  87. m1 := pool.Get()
  88. if m1 == nil {
  89. t.Fatal("Map pool returned nil")
  90. }
  91. m1["test"] = 123
  92. m1["another"] = 456
  93. if len(m1) != 2 {
  94. t.Error("Map not working correctly")
  95. }
  96. pool.Put(m1)
  97. // Test reuse and reset
  98. m2 := pool.Get()
  99. if len(m2) != 0 {
  100. t.Error("Map not properly reset")
  101. }
  102. pool.Put(m2)
  103. }
  104. // TestSlicePool tests the slice pool
  105. func TestSlicePool(t *testing.T) {
  106. pool := NewSlicePool[string](10, 5)
  107. // Test Get/Put cycle
  108. slice1 := pool.Get()
  109. if slice1 == nil {
  110. t.Fatal("Slice pool returned nil")
  111. }
  112. slice1 = append(slice1, "test1", "test2")
  113. if len(slice1) != 2 {
  114. t.Error("Slice not working correctly")
  115. }
  116. pool.Put(slice1)
  117. // Test reuse and reset
  118. slice2 := pool.Get()
  119. if len(slice2) != 0 {
  120. t.Error("Slice not properly reset")
  121. }
  122. pool.Put(slice2)
  123. }
  124. // TestPoolManager tests the pool manager functionality
  125. func TestPoolManager(t *testing.T) {
  126. manager := NewPoolManager()
  127. // Register a test pool
  128. testPool := NewStringBuilderPool(1024, 10)
  129. manager.RegisterPool("test_pool", testPool)
  130. // Retrieve the pool
  131. retrieved, exists := manager.GetPool("test_pool")
  132. if !exists {
  133. t.Fatal("Pool not found in manager")
  134. }
  135. if retrieved != testPool {
  136. t.Error("Retrieved pool is not the same as registered")
  137. }
  138. // Test non-existent pool
  139. _, exists = manager.GetPool("non_existent")
  140. if exists {
  141. t.Error("Non-existent pool was found")
  142. }
  143. // Test stats collection
  144. stats := manager.GetAllStats()
  145. if len(stats) == 0 {
  146. t.Error("No stats returned from manager")
  147. }
  148. }
  149. // TestPooledWorker tests the pooled worker functionality
  150. func TestPooledWorker(t *testing.T) {
  151. worker := NewPooledWorker()
  152. defer worker.Cleanup()
  153. testData := []byte("test data for processing")
  154. processCount := 0
  155. processor := func(data []byte, sb *strings.Builder) error {
  156. sb.WriteString(string(data))
  157. processCount++
  158. return nil
  159. }
  160. // Process data multiple times
  161. for i := 0; i < 5; i++ {
  162. err := worker.ProcessWithPools(testData, processor)
  163. if err != nil {
  164. t.Fatalf("Processing failed: %v", err)
  165. }
  166. }
  167. if processCount != 5 {
  168. t.Errorf("Expected 5 processing calls, got %d", processCount)
  169. }
  170. }
  171. // BenchmarkEnhancedObjectPool benchmarks the enhanced object pool
  172. func BenchmarkEnhancedObjectPool(b *testing.B) {
  173. pool := NewEnhancedObjectPool(
  174. func() *strings.Builder { return &strings.Builder{} },
  175. func(sb *strings.Builder) { sb.Reset() },
  176. 100,
  177. )
  178. b.ResetTimer()
  179. b.ReportAllocs()
  180. b.Run("WithPooling", func(b *testing.B) {
  181. for i := 0; i < b.N; i++ {
  182. sb := pool.Get()
  183. sb.WriteString("benchmark test data")
  184. _ = sb.String()
  185. pool.Put(sb)
  186. }
  187. })
  188. b.Run("WithoutPooling", func(b *testing.B) {
  189. for i := 0; i < b.N; i++ {
  190. sb := &strings.Builder{}
  191. sb.WriteString("benchmark test data")
  192. _ = sb.String()
  193. }
  194. })
  195. }
  196. // BenchmarkStringBuilderPool benchmarks the string builder pool
  197. func BenchmarkStringBuilderPool(b *testing.B) {
  198. pool := NewStringBuilderPool(1024, 100)
  199. b.ResetTimer()
  200. b.ReportAllocs()
  201. b.Run("PooledStringBuilder", func(b *testing.B) {
  202. for i := 0; i < b.N; i++ {
  203. sb := pool.Get()
  204. sb.WriteString("benchmark test data for string building operations")
  205. sb.WriteString(" with additional content to test performance")
  206. _ = sb.String()
  207. pool.Put(sb)
  208. }
  209. })
  210. b.Run("RegularStringBuilder", func(b *testing.B) {
  211. for i := 0; i < b.N; i++ {
  212. sb := &strings.Builder{}
  213. sb.Grow(1024)
  214. sb.WriteString("benchmark test data for string building operations")
  215. sb.WriteString(" with additional content to test performance")
  216. _ = sb.String()
  217. }
  218. })
  219. }
  220. // BenchmarkByteSlicePool benchmarks the byte slice pool
  221. func BenchmarkByteSlicePool(b *testing.B) {
  222. pool := NewByteSlicePool()
  223. testSizes := []int{64, 256, 1024, 4096}
  224. for _, size := range testSizes {
  225. b.Run(f("PooledSlice_%d", size), func(b *testing.B) {
  226. b.ResetTimer()
  227. b.ReportAllocs()
  228. for i := 0; i < b.N; i++ {
  229. slice := pool.Get(size)
  230. // Simulate some work
  231. for j := 0; j < cap(slice) && j < 100; j++ {
  232. slice = append(slice, byte(j))
  233. }
  234. pool.Put(slice)
  235. }
  236. })
  237. b.Run(f("RegularSlice_%d", size), func(b *testing.B) {
  238. b.ResetTimer()
  239. b.ReportAllocs()
  240. for i := 0; i < b.N; i++ {
  241. slice := make([]byte, 0, size)
  242. // Simulate some work
  243. for j := 0; j < cap(slice) && j < 100; j++ {
  244. slice = append(slice, byte(j))
  245. }
  246. }
  247. })
  248. }
  249. }
  250. // BenchmarkMemoryPoolConcurrency tests concurrent access to memory pools
  251. func BenchmarkMemoryPoolConcurrency(b *testing.B) {
  252. pool := NewStringBuilderPool(1024, 100)
  253. b.ResetTimer()
  254. b.ReportAllocs()
  255. b.RunParallel(func(pb *testing.PB) {
  256. for pb.Next() {
  257. sb := pool.Get()
  258. sb.WriteString("concurrent benchmark test data")
  259. _ = sb.String()
  260. pool.Put(sb)
  261. }
  262. })
  263. }
  264. // BenchmarkPooledWorkerPerformance benchmarks the pooled worker
  265. func BenchmarkPooledWorkerPerformance(b *testing.B) {
  266. worker := NewPooledWorker()
  267. defer worker.Cleanup()
  268. testData := []byte("benchmark test data for pooled worker performance testing with longer content")
  269. processor := func(data []byte, sb *strings.Builder) error {
  270. sb.WriteString(string(data))
  271. sb.WriteString(" processed")
  272. return nil
  273. }
  274. b.ResetTimer()
  275. b.ReportAllocs()
  276. b.Run("PooledWorker", func(b *testing.B) {
  277. for i := 0; i < b.N; i++ {
  278. _ = worker.ProcessWithPools(testData, processor)
  279. }
  280. })
  281. b.Run("DirectProcessing", func(b *testing.B) {
  282. for i := 0; i < b.N; i++ {
  283. sb := &strings.Builder{}
  284. sb.Grow(1024)
  285. sb.WriteString(string(testData))
  286. sb.WriteString(" processed")
  287. }
  288. })
  289. }
  290. // TestMemoryPoolGCPressure tests that pools reduce GC pressure
  291. func TestMemoryPoolGCPressure(t *testing.T) {
  292. if testing.Short() {
  293. t.Skip("Skipping GC pressure test in short mode")
  294. }
  295. pool := NewStringBuilderPool(1024, 50)
  296. // Force GC and get initial stats
  297. runtime.GC()
  298. var m1 runtime.MemStats
  299. runtime.ReadMemStats(&m1)
  300. // Perform operations with pooling
  301. for i := 0; i < 10000; i++ {
  302. sb := pool.Get()
  303. sb.WriteString("GC pressure test data")
  304. _ = sb.String()
  305. pool.Put(sb)
  306. }
  307. runtime.GC()
  308. var m2 runtime.MemStats
  309. runtime.ReadMemStats(&m2)
  310. pooledAllocations := m2.TotalAlloc - m1.TotalAlloc
  311. // Reset and test without pooling
  312. runtime.GC()
  313. runtime.ReadMemStats(&m1)
  314. for i := 0; i < 10000; i++ {
  315. sb := &strings.Builder{}
  316. sb.Grow(1024)
  317. sb.WriteString("GC pressure test data")
  318. _ = sb.String()
  319. }
  320. runtime.GC()
  321. runtime.ReadMemStats(&m2)
  322. directAllocations := m2.TotalAlloc - m1.TotalAlloc
  323. // Pooling should significantly reduce allocations
  324. if pooledAllocations >= directAllocations {
  325. t.Logf("Pooled allocations: %d bytes", pooledAllocations)
  326. t.Logf("Direct allocations: %d bytes", directAllocations)
  327. t.Error("Pooled allocations should be significantly less than direct allocations")
  328. }
  329. reductionRatio := float64(directAllocations-pooledAllocations) / float64(directAllocations)
  330. t.Logf("Memory allocation reduction: %.2f%%", reductionRatio*100)
  331. if reductionRatio < 0.5 { // Expect at least 50% reduction
  332. t.Errorf("Expected at least 50%% allocation reduction, got %.2f%%", reductionRatio*100)
  333. }
  334. }
  335. // TestGlobalPools tests the global pool instances
  336. func TestGlobalPools(t *testing.T) {
  337. // Test LogStringBuilderPool
  338. sb := LogStringBuilderPool.Get()
  339. if sb == nil {
  340. t.Error("LogStringBuilderPool returned nil")
  341. }
  342. sb.WriteString("global pool test")
  343. LogStringBuilderPool.Put(sb)
  344. // Test GlobalByteSlicePool
  345. slice := GlobalByteSlicePool.Get(1024)
  346. if cap(slice) < 1024 {
  347. t.Error("GlobalByteSlicePool returned slice with insufficient capacity")
  348. }
  349. GlobalByteSlicePool.Put(slice)
  350. // Test StringSlicePool
  351. strSlice := StringSlicePool.Get()
  352. if strSlice == nil {
  353. t.Error("StringSlicePool returned nil")
  354. }
  355. strSlice = append(strSlice, "test")
  356. StringSlicePool.Put(strSlice)
  357. // Test map pools
  358. stringMap := StringMapPool.Get()
  359. if stringMap == nil {
  360. t.Error("StringMapPool returned nil")
  361. }
  362. stringMap["test"] = "value"
  363. StringMapPool.Put(stringMap)
  364. stringIntMap := StringIntMapPool.Get()
  365. if stringIntMap == nil {
  366. t.Error("StringIntMapPool returned nil")
  367. }
  368. stringIntMap["test"] = 123
  369. StringIntMapPool.Put(stringIntMap)
  370. intStringMap := IntStringMapPool.Get()
  371. if intStringMap == nil {
  372. t.Error("IntStringMapPool returned nil")
  373. }
  374. intStringMap[123] = "test"
  375. IntStringMapPool.Put(intStringMap)
  376. }
  377. // Helper function for formatted strings
  378. func f(format string, args ...interface{}) string {
  379. return fmt.Sprintf(format, args...)
  380. }