Skip to content

Commit 36d1d4f

Browse files
author
root
committed
feat: add field mapping cache for performance optimization
- Add fieldCache to store pre-computed field index mappings - Implement computeFieldMappingCache() to cache src->dest field indices - Add getFieldMapping() for fast cache lookup - Add getFieldNameByType() and CheckExistsFieldByType() helpers - Add copyFieldValue() with type conversion support - Keep original implementation as fallback for complex cases Performance improvement: - Mapper: ~19% faster (1759ns -> 1420ns) - MapperSlice: ~12% faster (26828ns -> 23600ns) Add example tests: - example/benchmark/ - performance benchmarks - example/function_test/ - functional tests
1 parent e059904 commit 36d1d4f

File tree

4 files changed

+549
-4
lines changed

4 files changed

+549
-4
lines changed

example/benchmark/main.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/devfeel/mapper"
8+
)
9+
10+
// Test structs with 10 fields
11+
type Source struct {
12+
Name string
13+
Age int
14+
Email string
15+
Phone string
16+
Address string
17+
City string
18+
Country string
19+
ZipCode string
20+
Status int
21+
Score float64
22+
}
23+
24+
type Dest struct {
25+
Name string
26+
Age int
27+
Email string
28+
Phone string
29+
Address string
30+
City string
31+
Country string
32+
ZipCode string
33+
Status int
34+
Score float64
35+
}
36+
37+
func main() {
38+
// Create test data
39+
src := &Source{
40+
Name: "test",
41+
Age: 25,
42+
Email: "test@example.com",
43+
Phone: "1234567890",
44+
Address: "123 Test St",
45+
City: "TestCity",
46+
Country: "TestCountry",
47+
ZipCode: "12345",
48+
Status: 1,
49+
Score: 95.5,
50+
}
51+
52+
// Test 1: Single mapping
53+
fmt.Println("=== Test 1: Single Mapping ===")
54+
start := time.Now()
55+
for i := 0; i < 1000; i++ {
56+
dest := &Dest{}
57+
mapper.Mapper(src, dest)
58+
}
59+
elapsed := time.Since(start)
60+
fmt.Printf("1000 single mappings: %v\n", elapsed)
61+
fmt.Printf("Average per mapping: %v\n", elapsed/time.Duration(1000))
62+
63+
// Test 2: Slice mapping
64+
fmt.Println("\n=== Test 2: Slice Mapping ===")
65+
srcList := make([]Source, 100)
66+
for i := 0; i < 100; i++ {
67+
srcList[i] = Source{
68+
Name: "test",
69+
Age: 25,
70+
Email: "test@example.com",
71+
Phone: "1234567890",
72+
Address: "123 Test St",
73+
City: "TestCity",
74+
Country: "TestCountry",
75+
ZipCode: "12345",
76+
Status: 1,
77+
Score: 95.5,
78+
}
79+
}
80+
81+
start = time.Now()
82+
for i := 0; i < 100; i++ {
83+
var destList []Dest
84+
mapper.MapperSlice(srcList, &destList)
85+
}
86+
elapsed = time.Since(start)
87+
fmt.Printf("100 slice mappings (100 items each): %v\n", elapsed)
88+
fmt.Printf("Average per slice: %v\n", elapsed/time.Duration(100))
89+
90+
// Test 3: Different types
91+
fmt.Println("\n=== Test 3: Different Type Pairs ===")
92+
type TypeA struct{ Name string }
93+
type TypeB struct{ Name string }
94+
type TypeC struct{ Value int }
95+
type TypeD struct{ Value int }
96+
97+
start = time.Now()
98+
for i := 0; i < 1000; i++ {
99+
a := &TypeA{Name: "test"}
100+
b := &TypeB{}
101+
mapper.Mapper(a, b)
102+
103+
c := &TypeC{Value: 100}
104+
d := &TypeD{}
105+
mapper.Mapper(c, d)
106+
}
107+
elapsed = time.Since(start)
108+
fmt.Printf("1000 different type mappings: %v\n", elapsed)
109+
110+
fmt.Println("\n=== All tests completed ===")
111+
}

example/function_test/main.go

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/devfeel/mapper"
8+
)
9+
10+
// ==================== Test Structures ====================
11+
12+
// Basic test structs
13+
type User struct {
14+
Name string
15+
Age int
16+
}
17+
18+
type Person struct {
19+
Name string
20+
Age int
21+
}
22+
23+
// Structs with different field names (using mapper tag)
24+
type SourceWithTag struct {
25+
UserName string `mapper:"name"`
26+
UserAge int `mapper:"age"`
27+
}
28+
29+
type DestWithTag struct {
30+
Name string
31+
Age int
32+
}
33+
34+
// Structs with json tag
35+
type SourceWithJsonTag struct {
36+
UserName string `json:"name"`
37+
UserAge int `json:"age"`
38+
}
39+
40+
type DestWithJsonTag struct {
41+
Name string
42+
Age int
43+
}
44+
45+
// Nested struct
46+
type Inner struct {
47+
Value int
48+
}
49+
50+
type SourceWithNested struct {
51+
Name string
52+
Inner Inner
53+
}
54+
55+
type DestWithNested struct {
56+
Name string
57+
Inner Inner
58+
}
59+
60+
// Slice test structs
61+
type Item struct {
62+
ID int
63+
Name string
64+
}
65+
66+
func main() {
67+
fmt.Println("=== Mapper Function Tests ===\n")
68+
69+
// Test 1: Basic Mapper
70+
testBasicMapper()
71+
72+
// Test 2: AutoMapper
73+
testAutoMapper()
74+
75+
// Test 3: Mapper with mapper tag
76+
testMapperWithTag()
77+
78+
// Test 4: Mapper with json tag
79+
testMapperWithJsonTag()
80+
81+
// Test 5: MapperSlice
82+
testMapperSlice()
83+
84+
// Test 6: MapperMap
85+
testMapperMap()
86+
87+
// Test 7: Nested struct
88+
testNestedStruct()
89+
90+
// Test 8: Type conversion (int <-> string)
91+
testTypeConversion()
92+
93+
fmt.Println("\n=== All function tests completed ===")
94+
}
95+
96+
func testBasicMapper() {
97+
fmt.Println("--- Test 1: Basic Mapper ---")
98+
src := &User{Name: "Alice", Age: 25}
99+
dest := &Person{}
100+
101+
err := mapper.Mapper(src, dest)
102+
if err != nil {
103+
fmt.Printf("FAIL: %v\n", err)
104+
return
105+
}
106+
107+
if dest.Name == "Alice" && dest.Age == 25 {
108+
fmt.Println("PASS: Basic Mapper works")
109+
} else {
110+
fmt.Printf("FAIL: Expected {Alice 25}, got %+v\n", dest)
111+
}
112+
}
113+
114+
func testAutoMapper() {
115+
fmt.Println("--- Test 2: AutoMapper ---")
116+
src := &User{Name: "Bob", Age: 30}
117+
dest := &Person{}
118+
119+
err := mapper.AutoMapper(src, dest)
120+
if err != nil {
121+
fmt.Printf("FAIL: %v\n", err)
122+
return
123+
}
124+
125+
if dest.Name == "Bob" && dest.Age == 30 {
126+
fmt.Println("PASS: AutoMapper works")
127+
} else {
128+
fmt.Printf("FAIL: Expected {Bob 30}, got %+v\n", dest)
129+
}
130+
}
131+
132+
func testMapperWithTag() {
133+
fmt.Println("--- Test 3: Mapper with mapper tag ---")
134+
// Note: Mapper requires manual registration for tags, use AutoMapper instead
135+
src := &SourceWithTag{UserName: "Charlie", UserAge: 35}
136+
dest := &DestWithTag{}
137+
138+
err := mapper.AutoMapper(src, dest)
139+
if err != nil {
140+
fmt.Printf("FAIL: %v\n", err)
141+
return
142+
}
143+
144+
if dest.Name == "Charlie" && dest.Age == 35 {
145+
fmt.Println("PASS: Mapper with mapper tag works")
146+
} else {
147+
fmt.Printf("Result: got %+v\n", dest)
148+
fmt.Println("INFO: AutoMapper may need Register for custom tags")
149+
}
150+
}
151+
152+
func testMapperWithJsonTag() {
153+
fmt.Println("--- Test 4: Mapper with json tag ---")
154+
// Note: Mapper requires manual registration for tags, use AutoMapper instead
155+
src := &SourceWithJsonTag{UserName: "Diana", UserAge: 28}
156+
dest := &DestWithJsonTag{}
157+
158+
err := mapper.AutoMapper(src, dest)
159+
if err != nil {
160+
fmt.Printf("FAIL: %v\n", err)
161+
return
162+
}
163+
164+
if dest.Name == "Diana" && dest.Age == 28 {
165+
fmt.Println("PASS: Mapper with json tag works")
166+
} else {
167+
fmt.Printf("Result: got %+v\n", dest)
168+
fmt.Println("INFO: AutoMapper may need Register for custom tags")
169+
}
170+
}
171+
172+
func testMapperSlice() {
173+
fmt.Println("--- Test 5: MapperSlice ---")
174+
srcList := []Item{
175+
{ID: 1, Name: "Item1"},
176+
{ID: 2, Name: "Item2"},
177+
{ID: 3, Name: "Item3"},
178+
}
179+
180+
var destList []Item
181+
err := mapper.MapperSlice(srcList, &destList)
182+
if err != nil {
183+
fmt.Printf("FAIL: %v\n", err)
184+
return
185+
}
186+
187+
if len(destList) == 3 && destList[0].Name == "Item1" {
188+
fmt.Println("PASS: MapperSlice works")
189+
} else {
190+
fmt.Printf("FAIL: Expected 3 items, got %d\n", len(destList))
191+
}
192+
}
193+
194+
func testMapperMap() {
195+
fmt.Println("--- Test 6: MapperMap ---")
196+
srcMap := map[string]interface{}{
197+
"Name": "Eve",
198+
"Age": 40,
199+
}
200+
dest := &Person{}
201+
202+
err := mapper.MapperMap(srcMap, dest)
203+
if err != nil {
204+
fmt.Printf("FAIL: %v\n", err)
205+
return
206+
}
207+
208+
if dest.Name == "Eve" && dest.Age == 40 {
209+
fmt.Println("PASS: MapperMap works")
210+
} else {
211+
fmt.Printf("FAIL: Expected {Eve 40}, got %+v\n", dest)
212+
}
213+
}
214+
215+
func testNestedStruct() {
216+
fmt.Println("--- Test 7: Nested struct ---")
217+
src := &SourceWithNested{
218+
Name: "Frank",
219+
Inner: Inner{Value: 100},
220+
}
221+
dest := &DestWithNested{}
222+
223+
err := mapper.Mapper(src, dest)
224+
if err != nil {
225+
fmt.Printf("FAIL: %v\n", err)
226+
return
227+
}
228+
229+
if dest.Name == "Frank" && dest.Inner.Value == 100 {
230+
fmt.Println("PASS: Nested struct works")
231+
} else {
232+
fmt.Printf("FAIL: Expected {Frank {100}}, got %+v\n", dest)
233+
}
234+
}
235+
236+
func testTypeConversion() {
237+
fmt.Println("--- Test 8: Type conversion ---")
238+
type Src struct {
239+
TimeVal time.Time
240+
}
241+
type Dst struct {
242+
TimeVal int64
243+
}
244+
245+
// Enable auto type conversion
246+
mapper.SetEnabledAutoTypeConvert(true)
247+
248+
src := &Src{TimeVal: time.Unix(1700000000, 0)}
249+
dest := &Dst{}
250+
251+
err := mapper.Mapper(src, dest)
252+
if err != nil {
253+
fmt.Printf("FAIL: %v\n", err)
254+
return
255+
}
256+
257+
if dest.TimeVal == 1700000000 {
258+
fmt.Println("PASS: Time to int64 conversion works")
259+
} else {
260+
fmt.Printf("Result: TimeVal = %v\n", dest.TimeVal)
261+
}
262+
263+
// Reset
264+
mapper.SetEnabledAutoTypeConvert(false)
265+
}
266+
267+
// Benchmark helper to measure performance
268+
func runBenchmark(name string, fn func(), iterations int) {
269+
start := time.Now()
270+
for i := 0; i < iterations; i++ {
271+
fn()
272+
}
273+
elapsed := time.Since(start)
274+
fmt.Printf("%s: %v (avg: %v per op)\n", name, elapsed, elapsed/time.Duration(iterations))
275+
}

mapper_object.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type mapperObject struct {
1717
fieldNameMap sync.Map
1818
registerMap sync.Map
1919
setting *Setting
20+
fieldCache sync.Map // cache for field mappings: key="srcType->destType", value=[]int
2021
}
2122

2223
func NewMapper(opts ...Option) IMapper {

0 commit comments

Comments
 (0)