1# -*- coding: utf-8 -*- 2# ------------------------------------------------------------------------------ 3# Name: testPerformance.py 4# Purpose: Tests keep track of long-term performance targets 5# 6# Authors: Christopher Ariza 7# 8# Copyright: Copyright © 2009-2011 Michael Scott Cuthbert and the music21 Project 9# License: BSD, see license.txt 10# ------------------------------------------------------------------------------ 11''' 12This module defines a number of performance test. 13 Results for these performances are stored and dated, 14 and used to track long-term performance changes. 15 16This file is not run with the standard test battery presently. 17''' 18 19 20import unittest 21 22import music21 23from music21 import common, corpus 24from music21.musicxml.m21ToXml import GeneralObjectExporter as GEX 25 26from music21 import environment 27_MOD = 'test.testPerformance' 28environLocal = environment.Environment(_MOD) 29 30# ------------------------------------------------------------------------------ 31 32 33class Test(unittest.TestCase): 34 35 def runStreamIterationByIterator(self): 36 '''Stream iteration by iterator 37 ''' 38 from music21 import note 39 from music21 import stream 40 # create a stream with 750 notes, 250 rests 41 s = stream.Stream() 42 for i in range(1000): 43 n = note.Note() 44 s.append(n) 45 for i in range(500): 46 r = note.Rest() 47 s.append(r) 48 49 for i in range(100): 50 for j in s: # this will create an iterator instances 51 pass 52 53 def runStreamIterationByElements(self): 54 '''Stream iteration by .elements access 55 ''' 56 from music21 import note 57 from music21 import stream 58 # create a stream with 750 notes, 250 rests 59 s = stream.Stream() 60 for i in range(1000): 61 n = note.Note() 62 s.append(n) 63 for i in range(500): 64 r = note.Rest() 65 s.append(r) 66 67 for i in range(100): 68 for j in s.elements: # this will create an iterator instances 69 pass 70 71 def runGetElementsByClassType(self): 72 '''Getting elements by class type 73 ''' 74 from music21 import note 75 from music21 import stream 76 77 # create a stream with 750 notes, 250 rests 78 s = stream.Stream() 79 for i in range(1000): 80 n = note.Note() 81 s.append(n) 82 for i in range(500): 83 r = note.Rest() 84 s.append(r) 85 86 for i in range(2): 87 post = s.recurse().getElementsByClass([note.Rest, note.Note]) 88 self.assertEqual(len(post), 1500) 89 90 def runGetElementsByClassString(self): 91 '''Getting elements by string 92 ''' 93 from music21 import note 94 from music21 import stream 95 96 # create a stream with 750 notes, 250 rests 97 s = stream.Stream() 98 for i in range(1000): 99 n = note.Note() 100 s.append(n) 101 for i in range(500): 102 r = note.Rest() 103 s.append(r) 104 105 for i in range(2): 106 post = s.recurse().getElementsByClass(['Rest', 'Note']) 107 self.assertEqual(len(post), 1500) 108 109 def runParseBeethoven(self): 110 '''Loading file: beethoven/opus59no2/movement3 111 ''' 112 junk = corpus.parse('beethoven/opus59no2/movement3', forceSource=True) 113 114 def runMusicxmlOutPartsBeethoven(self): 115 '''Loading file and rendering musicxml output for each part: beethoven/opus59no2/movement3 116 ''' 117 x = corpus.parse('beethoven/opus59no2/movement3', forceSource=True) 118 # problem: doing each part is much faster than the whole score 119 for p in x.parts: 120 junk = GEX().parse(p) 121 122 def runMusicxmlOutScoreBeethoven(self): 123 ''' 124 Loading file and rendering musicxml output of complete score: beethoven/opus59no2/movement3 125 ''' 126 x = corpus.parse('beethoven/opus59no2/movement3', forceSource=True) 127 # problem: doing each part is much faster than the whole score 128 junk = GEX().parse(x) 129 130 def runParseHaydn(self): 131 '''Loading file: haydn/opus74no1/movement3 132 ''' 133 junk = corpus.parse('haydn/opus74no1/movement3', forceSource=True) 134 135 def runParseSchumann(self): 136 '''Loading file: schumann/opus41no1/movement2 137 ''' 138 junk = corpus.parse('schumann/opus41no1/movement2', forceSource=True) 139 140 def runParseLuca(self): 141 ''' 142 Loading file: luca/gloria 143 ''' 144 junk = corpus.parse('luca/gloria', forceSource=True) 145 146 def runMusicxmlOutLuca(self): 147 ''' 148 Loading file and rendering musicxml output: luca/gloria 149 ''' 150 x = corpus.parse('luca/gloria', forceSource=True) 151 junk = GEX().parse(x) 152 153 def runCreateTimeSignatures(self): 154 '''Creating 500 TimeSignature objects 155 ''' 156 from music21 import meter 157 tsStr = ['4/4', '4/4', '4/4', '3/4', '3/4', '2/4', '2/4', '2/2', 158 '2/2', '3/8', '6/8', '9/8', '5/4', '12/8'] 159 160 for i in range(500): 161 meter.TimeSignature(tsStr[i % len(tsStr)]) 162 163 def runCreateDurations(self): 164 ''' 165 Creating 10000 Duration objects 166 ''' 167 from music21 import duration 168 qlList = [4, 2, 1, 0.5, 1 / 3, 0.25, 0.125] 169 170 for i in range(10000): 171 ql = qlList[i % len(qlList)] 172 d = duration.Duration() 173 d.quarterLength = ql 174 junk = d.quarterLength 175 176 def runCreatePitches(self): 177 ''' 178 Creating 50000 Pitch objects 179 ''' 180 from music21 import pitch 181 pList = [1.5, 5, 20.333333, 8, 2.5, 'A#', 'b`', 'c6#~'] 182 183 for i in range(50000): 184 inputPName = pList[i % len(pList)] 185 p = pitch.Pitch(inputPName) 186 p.transpose('p5', inPlace=True) 187 188 def runParseABC(self): 189 '''Creating loading a large multiple work abc file (han1) 190 ''' 191 dummy = corpus.parse('essenFolksong/han1') 192 193 def runGetElementsByContext(self): 194 '''Test getting elements by context from a Stream 195 ''' 196 s = corpus.parse('bwv66.6') 197 # create a few secondary streams to add more sites 198 unused_flat = s.flatten() 199 unused_notes = s.flatten().notes 200 201 for p in s.parts: 202 for m in p.getElementsByClass('Measure'): 203 if m.number == 0: 204 continue 205 post = m.getContextByClass('Clef') 206 assert post is not None 207 post = m.getContextByClass('TimeSignature') 208 assert post is not None 209 post = m.getContextByClass('KeySignature') 210 assert post is not None 211 212 for n in m.notesAndRests: 213 post = n.getContextByClass('Clef') 214 assert post is not None 215 post = n.getContextByClass('TimeSignature') 216 assert post is not None 217 post = n.getContextByClass('KeySignature') 218 assert post is not None 219 220 def runGetElementsByPrevious(self): 221 '''Test getting elements by using the previous method 222 ''' 223 s = corpus.parse('bwv66.6') 224 # create a few secondary streams to add more sites 225 unused_flat = s.flatten() 226 unused_notes = s.flatten().notes 227 228 for p in s.parts: 229 for m in p.getElementsByClass('Measure'): 230 if m.number == 0: 231 continue 232 post = m.previous('Clef') 233 assert post is not None 234 post = m.previous('TimeSignature') 235 assert post is not None 236 post = m.previous('KeySignature') 237 assert post is not None 238 239 for n in m.notesAndRests: 240 post = n.getContextByClass('Clef') 241 assert post is not None 242 post = n.getContextByClass('TimeSignature') 243 assert post is not None 244 post = n.getContextByClass('KeySignature') 245 assert post is not None 246 247 def runParseMonteverdiRNText(self): 248 '''Loading file: beethoven/opus59no2/movement3 249 ''' 250 unused = corpus.parse('monteverdi/madrigal.5.3.rntxt', forceSource=True) 251 252 # -------------------------------------------------------------------------- 253 def testTimingTolerance(self): 254 ''' 255 Test the performance of methods defined above, 256 comparing the resulting time to the time obtained in past runs. 257 258 This should not produce errors as such, but is used to provide reference 259 if overall performance has changed. 260 ''' 261 # provide work and expected min/max in seconds 262 for testMethod, best in [ 263 264 (self.runGetElementsByPrevious, 265 { 266 '2011.11.29': 4.69, 267 }), 268 269 (self.runGetElementsByContext, 270 { 271 '2010.11.10': 7.3888170, 272 '2010.11.11': 3.96121883392, 273 }), 274 275 276 # 277 # 278 # 279 # # (self.runParseABC, 280 # # { 281 # # '2010.10.20': 38.6760668755, 282 # # '2010.10.21': 35.4297668934, 283 # # }), 284 # 285 # 286 # (self.runCreateDurations, 287 # { 288 # '2010.10.07': 0.201117992401, 289 # 290 # }), 291 # 292 # (self.runCreateTimeSignatures, 293 # { 294 # '2010.10.07': 2.88308691978, 295 # '2010.10.08': 1.40892004967 , 296 # 297 # }), 298 # 299 # 300 # (self.runStreamIterationByIterator, 301 # {'2010.09.20': 2.2524, 302 # '2010.10.07': 1.8214, 303 # }), 304 # 305 # (self.runStreamIterationByElements, 306 # {'2010.09.20': 0.8317, 307 # }), 308 # 309 # 310 # (self.runGetElementsByClassType, 311 # {'2010.09.20': 3.28, 312 # }), 313 # 314 # (self.runGetElementsByClassString, 315 # {'2010.09.20': 3.22, 316 # }), 317 # 318 # (self.runParseBeethoven, 319 # {'2009.12.14': 7.42, 320 # '2009.12.15': 6.686, 321 # '2010.06.24': 7.475, 322 # '2010.07.08': 3.562, 323 # }), 324 # 325 # (self.runMusicxmlOutPartsBeethoven, 326 # {'2010.09.20': 7.706, 327 # }), 328 # 329 # (self.runMusicxmlOutScoreBeethoven, 330 # {'2010.09.20': 33.273, 331 # '2010.10.07': 11.9290, 332 # }), 333 334 335 # 336 # (self.runCreatePitches, 337 # {'2011.04.12': 31.071, 338 # }), 339 340 341 # (self.runParseHaydn, 342 # {'2009.12.14': 4.08, 343 # '2009.12.15': 3.531, 344 # '2010.06.24': 3.932, 345 # '2010.07.08': 1.935, 346 # }), 347 # (self.runParseSchumann, 348 # {'2009.12.14': 5.88, 349 # '2009.12.15': 5.126, 350 # '2010.06.24': 5.799, 351 # '2010.07.08': 2.761, 352 # }), 353 # (self.runParseLuca, 354 # {'2009.12.14': 3.174, 355 # '2009.12.15': 2.954, 356 # '2010.06.24': 3.063, 357 # '2010.07.08': 1.508, 358 # }), 359 # 360 # (self.runMusicxmlOutLuca, 361 # {'2010.09.20': 8.372, 362 # '2010.10.07': 4.5613, 363 # }), 364 # 365 366 # (self.runParseMonteverdiRNText, 367 # {'2011.02.27': 6.411, 368 # '2011.02.28': 2.944, 369 # }), 370 371 ]: # end of long for loop 372 373 t = common.Timer() 374 t.start() 375 # call the test 376 testMethod() 377 t.stop() 378 dur = t() 379 items = list(best.items()) 380 items.sort() 381 items.reverse() 382 environLocal.printDebug(['\n\ntiming tolerance for:', 383 str(testMethod.__doc__.strip()), 384 '\nthis run:', dur, '\nbest runs:', 385 [f'{x}: {y}' for x, y in items], '\n' 386 ] 387 ) 388 # self.assertEqual(True, dur <= max) # performance test 389 390 391if __name__ == '__main__': 392 import sys 393 394 if len(sys.argv) == 1: # normal conditions 395 # sys.arg test options will be used in mainTest() 396 music21.mainTest(Test) 397 398 399