/ bench / yamlBench.nim
yamlBench.nim
  1  import "../yaml", commonBench
  2  import math, strutils, stopwatch, terminal, algorithm, random, streams
  3  
  4  from nimlets_yaml import objKind
  5  
  6  type
  7      Level = tuple
  8          kind: YamlNodeKind
  9          len: int
 10  
 11  proc genString(maxLen: int): string =
 12      let len = random(maxLen)
 13      result = ""
 14      for i in 1 .. len: result.add(cast[char](random(127 - 32) + 32))
 15  
 16  proc genBlockString(): string =
 17      let lines = 5 + random(10)
 18      let flow = random(2) == 0
 19      result = ""
 20      for i in 1 .. lines:
 21          let lineLen = 32 + random(12)
 22          for i in i .. lineLen: result.add(cast[char](random(127 - 33) + 33))
 23          result.add(if flow: ' ' else: '\l')
 24      result.add('\l')
 25  
 26  proc genKey(): string =
 27      let genPossiblePlainKey = random(1.0) < 0.75
 28      if genPossiblePlainKey:
 29          result = ""
 30          let len = random(24) + 1
 31          for i in 1 .. len:
 32              let c = random(26 + 26 + 10)
 33              if c < 26: result.add(char(c + 65))
 34              elif c < 52: result.add(char(c + 97 - 26))
 35              else: result.add(char(c + 48 - 52))
 36      else: result = genString(31) & char(random(26) + 65)
 37  
 38  proc genYamlString(size: int, maxStringLen: int,
 39                     style: PresentationStyle): string =
 40      ## Generates a random YAML string.
 41      ## size is in KiB, mayStringLen in characters.
 42  
 43      randomize(size * maxStringLen * ord(style))
 44  
 45      let targetSize = size * 1024
 46      var
 47          target = newStringStream()
 48          input = iterator(): YamlStreamEvent =
 49              var
 50                  levels = newSeq[Level]()
 51                  curSize = 1
 52              levels.add((kind: yMapping, len: 0))
 53              yield startDocEvent()
 54              yield startMapEvent()
 55  
 56              while levels.len > 0:
 57                  let
 58                      objectCloseProbability =
 59                          float(levels[levels.high].len + levels.high) * 0.025
 60                      closeObject = random(1.0) <= objectCloseProbability
 61  
 62                  if (closeObject and levels.len > 1) or curSize > targetSize:
 63                      case levels[levels.high].kind
 64                      of yMapping: yield endMapEvent()
 65                      of ySequence: yield endSeqEvent()
 66                      else: assert(false)
 67                      curSize += 1
 68                      discard levels.pop()
 69                      continue
 70  
 71                  levels[levels.high].len += 1
 72                  if levels[levels.high].kind == yMapping:
 73                      let key = genKey()
 74                      yield scalarEvent(key)
 75  
 76                  let
 77                      objectValueProbability =
 78                          0.8 / float(levels.len * levels.len)
 79                      generateObjectValue = random(1.0) <= objectValueProbability
 80                      hasTag = random(2) == 0
 81                  var tag = yTagQuestionMark
 82  
 83                  if generateObjectValue:
 84                      let objectKind = if random(3) == 0: ySequence else: yMapping
 85                      case objectKind
 86                      of yMapping:
 87                          if hasTag: tag = yTagMapping
 88                          yield startMapEvent(tag)
 89                      of ySequence:
 90                          if hasTag: tag = yTagSequence
 91                          yield startSeqEvent(tag)
 92                      else: assert(false)
 93                      curSize += 1
 94                      levels.add((kind: objectKind, len: 0))
 95                  else:
 96                      var s: string
 97                      case random(11)
 98                      of 0..4:
 99                          s = genString(maxStringLen)
100                          if hasTag: tag = yTagString
101                      of 5:
102                          s = genBlockString()
103                      of 6..7:
104                          s = $random(32000)
105                          if hasTag: tag = yTagInteger
106                      of 8..9:
107                          s = $(random(424242.4242) - 212121.21)
108                          if hasTag: tag = yTagFloat
109                      of 10:
110                          case random(3)
111                          of 0:
112                              s = "true"
113                              if hasTag: tag = yTagBoolean
114                          of 1:
115                              s = "false"
116                              if hasTag: tag = yTagBoolean
117                          of 2:
118                              s = "null"
119                              if hasTag: tag = yTagNull
120                          else: discard
121                      else: discard
122  
123                      yield scalarEvent(s, tag)
124                      curSize += s.len
125              yield endDocEvent()
126      var yStream = initYamlStream(input)
127      present(yStream, target, initExtendedTagLibrary(),
128              defineOptions(style=style, outputVersion=ov1_1))
129      result = target.data
130  
131  var
132      cYaml1k, cYaml10k, cYaml100k, cLibYaml1k, cLibYaml10k, cLibYaml100k,
133          cYaml1m, cLibYaml1m: int64
134      yaml1k   = genYamlString(1, 32, psDefault)
135      yaml10k  = genYamlString(10, 32, psDefault)
136      yaml100k = genYamlString(100, 32, psDefault)
137      yaml1m  = genYamlString(1000, 32, psDefault)
138      tagLib   = initExtendedTagLibrary()
139      parser = newYamlParser(tagLib)
140  
141  block:
142      multibench(cYaml1k, 100):
143          let res = loadDOM(yaml1k)
144          assert res.root.kind == yMapping
145  
146  block:
147      multibench(cYaml10k, 100):
148          let res = loadDOM(yaml10k)
149          assert res.root.kind == yMapping
150  
151  block:
152      multibench(cYaml100k, 100):
153          let res = loadDOM(yaml100k)
154          assert res.root.kind == yMapping
155  
156  block:
157      multibench(cYaml1m, 2):
158          let res = loadDOM(yaml1m)
159          assert res.root.kind == yMapping
160  
161  block:
162      multibench(cLibYaml1k, 100):
163          let res = nimlets_yaml.load(yaml1k)
164          assert res[0].objKind == nimlets_yaml.YamlObjKind.Map
165  
166  block:
167      multibench(cLibYaml10k, 100):
168          let res = nimlets_yaml.load(yaml10k)
169          assert res[0].objKind == nimlets_yaml.YamlObjKind.Map
170  
171  block:
172      multibench(cLibYaml100k, 100):
173          let res = nimlets_yaml.load(yaml100k)
174          assert res[0].objKind == nimlets_yaml.YamlObjKind.Map
175  
176  block:
177      multibench(cLibYaml1m, 2):
178          let res = nimlets_yaml.load(yaml1m)
179          assert res[0].objKind == nimlets_yaml.YamlObjKind.Map
180  
181  proc writeResult(caption: string, num: int64) =
182      styledWriteLine(stdout, resetStyle, caption, fgGreen, $num, resetStyle, "μs")
183  
184  setForegroundColor(fgWhite)
185  
186  writeStyled "Benchmark: Processing YAML input\n"
187  writeStyled "================================\n"
188  writeStyled "1k input\n--------\n"
189  writeResult "NimYAML: ", cYaml1k div 1000
190  writeResult "LibYAML: ", cLibYaml1k div 1000
191  setForegroundColor(fgWhite)
192  writeStyled "10k input\n---------\n"
193  writeResult "NimYAML: ", cYaml10k div 1000
194  writeResult "LibYAML: ", cLibYaml10k div 1000
195  setForegroundColor(fgWhite)
196  writeStyled "100k input\n----------\n"
197  writeResult "NimYAML: ", cYaml100k div 1000
198  writeResult "LibYAML: ", cLibYaml100k div 1000
199  setForegroundColor(fgWhite)
200  writeStyled "1m input\n---------\n"
201  writeResult "NimYAML: ", cYaml1m div 1000
202  writeResult "LibYAML: ", cLibYaml1m div 1000