perf.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # Copyright Amethyst Reese
  2. # Licensed under the MIT license
  3. """
  4. Simple perf tests for aiosqlite and the asyncio run loop.
  5. """
  6. import string
  7. import tempfile
  8. import time
  9. from unittest import IsolatedAsyncioTestCase as TestCase
  10. import aiosqlite
  11. from .smoke import setup_logger
  12. TEST_DB = ":memory:"
  13. TARGET = 2.0
  14. RESULTS = {}
  15. def timed(fn, name=None):
  16. """
  17. Decorator for perf testing a block of async code.
  18. Expects the wrapped function to return an async generator.
  19. The generator should do setup, then yield when ready to start perf testing.
  20. The decorator will then pump the generator repeatedly until the target
  21. time has been reached, then close the generator and print perf results.
  22. """
  23. name = name or fn.__name__
  24. async def wrapper(*args, **kwargs):
  25. gen = fn(*args, **kwargs)
  26. await gen.asend(None)
  27. count = 0
  28. before = time.time()
  29. while True:
  30. count += 1
  31. value = time.time() - before < TARGET
  32. try:
  33. if value:
  34. await gen.asend(value)
  35. else:
  36. await gen.aclose()
  37. break
  38. except StopAsyncIteration:
  39. break
  40. except Exception as e:
  41. print(f"exception occurred: {e}")
  42. return
  43. duration = time.time() - before
  44. RESULTS[name] = (count, duration)
  45. return wrapper
  46. class PerfTest(TestCase):
  47. @classmethod
  48. def setUpClass(cls):
  49. print(f"Running perf tests for at least {TARGET:.1f}s each...")
  50. setup_logger()
  51. @classmethod
  52. def tearDownClass(cls):
  53. print(f"\n{'Perf Test':<25} Iterations Duration {'Rate':>11}")
  54. for name in sorted(RESULTS):
  55. count, duration = RESULTS[name]
  56. rate = count / duration
  57. name = name.replace("test_", "")
  58. print(f"{name:<25} {count:>10} {duration:>7.1f}s {rate:>9.1f}/s")
  59. @timed
  60. async def test_connection_memory(self):
  61. while True:
  62. yield
  63. async with aiosqlite.connect(TEST_DB):
  64. pass
  65. @timed
  66. async def test_connection_file(self):
  67. with tempfile.NamedTemporaryFile(delete=False) as tf:
  68. path = tf.name
  69. tf.close()
  70. async with aiosqlite.connect(path) as db:
  71. await db.execute(
  72. "create table perf (i integer primary key asc, k integer)"
  73. )
  74. await db.execute("insert into perf (k) values (2), (3)")
  75. await db.commit()
  76. while True:
  77. yield
  78. async with aiosqlite.connect(path):
  79. pass
  80. @timed
  81. async def test_atomics(self):
  82. async with aiosqlite.connect(TEST_DB) as db:
  83. await db.execute("create table perf (i integer primary key asc, k integer)")
  84. await db.execute("insert into perf (k) values (2), (3)")
  85. await db.commit()
  86. while True:
  87. yield
  88. async with db.execute("select last_insert_rowid()") as cursor:
  89. await cursor.fetchone()
  90. @timed
  91. async def test_inserts(self):
  92. async with aiosqlite.connect(TEST_DB) as db:
  93. await db.execute("create table perf (i integer primary key asc, k integer)")
  94. await db.commit()
  95. while True:
  96. yield
  97. await db.execute("insert into perf (k) values (1), (2), (3)")
  98. await db.commit()
  99. @timed
  100. async def test_insert_ids(self):
  101. async with aiosqlite.connect(TEST_DB) as db:
  102. await db.execute("create table perf (i integer primary key asc, k integer)")
  103. await db.commit()
  104. while True:
  105. yield
  106. cursor = await db.execute("insert into perf (k) values (1)")
  107. await cursor.execute("select last_insert_rowid()")
  108. await cursor.fetchone()
  109. await db.commit()
  110. @timed
  111. async def test_insert_macro_ids(self):
  112. async with aiosqlite.connect(TEST_DB) as db:
  113. await db.execute("create table perf (i integer primary key asc, k integer)")
  114. await db.commit()
  115. while True:
  116. yield
  117. await db.execute_insert("insert into perf (k) values (1)")
  118. await db.commit()
  119. @timed
  120. async def test_select(self):
  121. async with aiosqlite.connect(TEST_DB) as db:
  122. await db.execute("create table perf (i integer primary key asc, k integer)")
  123. for i in range(100):
  124. await db.execute("insert into perf (k) values (%d)" % (i,))
  125. await db.commit()
  126. while True:
  127. yield
  128. cursor = await db.execute("select i, k from perf")
  129. assert len(await cursor.fetchall()) == 100
  130. @timed
  131. async def test_select_macro(self):
  132. async with aiosqlite.connect(TEST_DB) as db:
  133. await db.execute("create table perf (i integer primary key asc, k integer)")
  134. for i in range(100):
  135. await db.execute("insert into perf (k) values (%d)" % (i,))
  136. await db.commit()
  137. while True:
  138. yield
  139. assert len(await db.execute_fetchall("select i, k from perf")) == 100
  140. async def test_iterable_cursor_perf(self):
  141. async with aiosqlite.connect(TEST_DB) as db:
  142. await db.execute(
  143. "create table ic_perf ("
  144. "i integer primary key asc, k integer, a integer, b integer, c char(16))"
  145. )
  146. for batch in range(128): # add 128k rows
  147. r_start = batch * 1024
  148. await db.executemany(
  149. "insert into ic_perf (k, a, b, c) values(?, 1, 2, ?)",
  150. [
  151. *[
  152. (i, string.ascii_lowercase)
  153. for i in range(r_start, r_start + 1024)
  154. ]
  155. ],
  156. )
  157. await db.commit()
  158. async def test_perf(chunk_size: int):
  159. while True:
  160. async with db.execute("SELECT * FROM ic_perf") as cursor:
  161. cursor.iter_chunk_size = chunk_size
  162. async for _ in cursor:
  163. yield
  164. for chunk_size in [2**i for i in range(4, 11)]:
  165. await timed(test_perf, f"iterable_cursor @ {chunk_size}")(chunk_size)