Go语言 我应该使用什么样的数据结构来使我的“日历”API具有最大的性能?

23c0lvtd  于 2023-08-01  发布在  Go
关注(0)|答案(1)|浏览(112)

我有一个微服务,为员工提供工作日历。原始数据具有以下文件结构:

calendars / [year] / [employee].json

字符串
[employee].json的内容:

{
  // [date]: [hours]
  "2023-02-01": 7,
  "2023-02-02": 7,
}


使用Go,我将所有数据转换为这两个类型为CalendarFastCalendarSlow的变量:

type Username = string
type Date = string
type Year = string
type Hours = float64

type CalendarFast map[Username]map[Year][]struct {
    Time  time.Time // "2022-01-01"
    Hours Hours // 7
}

type CalendarSlow map[Username]map[Year]map[Date]Hours


例如,如果我想从我的API返回给定日期范围内的所有小时数,我可以编写这两个基准来测试哪种数据结构更好:

func BenchmarkNew(b *testing.B) {
    c := make(CalendarFast)
    c.Update(context.TODO())

    from, _ := time.Parse("2006-01-02", "2023-01-01")
    to, _ := time.Parse("2006-01-02", "2023-02-01")

    isInRange := func(t, from, to time.Time) bool {
        return (t.After(from) && t.Before(to)) || t.Equal(from) || t.Equal(to)
    }

    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        // result is map[username]map[date]hours
        result := make(map[string]map[string]float64)
        for username, years := range c {
            if _, ok := result[username]; !ok {
                result[username] = make(map[string]float64)
            }

            for _, dates := range years {
                for i, n := 0, len(dates); i < n; i++ {
                    if isInRange(dates[i].Time, from, to) {
                        result[username][dates[i].Time.String()] = dates[i].Hours
                    }
                }
            }
        }
    }
}

func BenchmarkOld(b *testing.B) {
    c := make(CalendarSlow)
    c.Update(context.TODO())

    from, _ := time.Parse("2006-01-02", "2023-01-01")
    to, _ := time.Parse("2006-01-02", "2023-02-01")

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        result := make(map[string]map[string]float64)
        for username, years := range c {
            if _, ok := result[username]; !ok {
                result[username] = make(map[string]float64)
            }

            for _, items := range years {
                for date, hours := range items {
                    t, err := time.Parse("2006-01-02", date)
                    if err != nil {
                        continue
                    }

                    if (t.After(from) && t.Before(to)) || t.Equal(from) || t.Equal(to) {
                        result[username][date] = hours
                    }
                }
            }
        }
    }
}


我有以下基准测试,它表明在数组中搜索范围要快得多,搜索map中的每个元素:

BenchmarkNew-4  285    4208045 ns/op  692965 B/op  6303 allocs/op
BenchmarkOld-4   39   29672935 ns/op  621877 B/op  1407 allocs/op


我的问题是:如何进一步提高性能?我是否应该将数据结构更改为另一个?
我需要速度,因为我收到了很多对我当前PHP API的调用,并发现Go中的微服务可以轻松处理1 k rps,而k8s中只有10 m cpu和64 Mib内存
我已经对“time.Time”方法的数组感到满意,但对另一种实现感到好奇。
UPD:
| 迭代|时间| time |
| --|--| ------------ |
| 二百二十九|5259548 ns/op| 5259548 ns/op |
| 四一四|2892932 ns/op| 2892932 ns/op |

bnl4lu3b

bnl4lu3b1#

根据您的描述,数据是静态的,您只在程序启动时阅读源文件一次。
为什么不对每个员工的日期数组进行排序,这样一旦超过了结束日期,就可以停止检查其余的日期?
为什么yearMap存在?。还有一件事要重复。
为什么要将所有日期存储为字符串,然后每次要按日期范围进行过滤时都要解析它们?为什么不使用time.Time?在内存中更有效,并且不需要一遍又一遍地解析。
我将使用map[Username][]struct{date time.Time, hours int},在读取一次后对数组进行排序,然后像这样迭代:

for username, dates := range c {
   for shift := range dates {
      if shift.date.Before(from) {
         continue
      } else if shift.date.After(to) {
         break
      }
      if _, ok := result[username]; !ok {
         result[username] = make(map[string]float64)
      }
      result[username][shift.date] = shift.hours
   }
}

字符串

相关问题