6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 13:33:07 +01:00
firebird-qa/tests/bugs/gh_7382_test.py

209 lines
7.9 KiB
Python

#coding:utf-8
"""
ID: issue-7382
ISSUE: 7382
TITLE: Performance improvement for BLOB copying
DESCRIPTION:
We create TWO stored procedures.
Both of them create blob ("b_src") with length = <N_BLOB_FINAL_LEN>.
First procedure creates b_src in usual way: b_src = lpad('',n_len, 'a').
Second procedure does this in loop with incrementing blob for one character, like this: b_src = blob_append(b_src, 'a').
This means that in second SP blob will be created as STREAM (unlike it is in 1st proc).
Then we make copy of this blob <N_COUNT_PER_MEASURE> times to another blob (in each SP).
Duration of this SP execution is measured as difference between psutil.Process(fb_pid).cpu_times() counters.
We do these measures <N_MEASURES> times for each SP, and each result is added to the list.
Finally, we evaluate RATIO between duration of SP-1 and SP-2, and then obtain MEDIAN for that.
This median must not exceed <MAX_RATIO> threshold.
NB! It is desirable that test database will use page cache with big enough size
(in order to avoid impact of blob creation on IO performance).
Because of this, test DB will use info from predefined <REQUIRED_ALIAS> in the $QA_ROOT/files/qa-database.conf
Before this improvement median ratio was 5.0 ... 6.0 (5.0.0.821). After this improvement (5.0.0.824) it is 1.25 ... 1.33.
Values of median are equal on Windows and Linux, thus MAX_RATIO = 2 for both OS.
NOTES:
[16.02.2023] pzotov
1. One need to be sure that firebird.conf does NOT contain DatabaseAccess = None.
2. Test uses pre-created databases.conf which has alias defined by variable REQUIRED_ALIAS.
Database file for that alias must NOT exist in the QA_root/files/qa/ subdirectory: it will be created here.
Content of databases.conf must be taken from $QA_ROOT/files/qa-databases.conf (one need to replace
it before every test session).
Discussed with pcisar, letters since 30-may-2022 13:48, subject:
"new qa, core_4964_test.py: strange outcome when use... shutil.copy() // comparing to shutil.copy2()"
3. Value of REQUIRED_ALIAS must be EXACTLY the same as alias specified in the pre-created databases.conf
(for LINUX this equality is case-sensitive, even when aliases are compared!)
Checked on:
LINUX Debian 10, FB 5.0.0.930 SS/CS
Windows 8.1, FB 5.0.0.824 SS/CS
[19.07.2023] pzotov
Increased values of N_BLOB_FINAL_LEN and N_COUNT_PER_MEASURE, reduced value of N_MEASURES: every iteration must perform
significant volume of job in order its "cpu_time().user" counter will be not too small/inaccurate.
Changes caused by permanent fails when FB is running as SuperServer. Discussed with Vlad.
Checked again on 5.0.0.1121, mode = SuperServer
1) Windows-10 v 21H2 bld 19044.3208 (IBSurgeon test server):
sp_blob_copy_1 sp_blob_copy_2 ratio
0.234375 0.171875 1.363636
0.218750 0.171875 1.272727
0.250000 0.140625 1.777778
0.265625 0.203125 1.307692
0.234375 0.125000 1.875000
0.250000 0.171875 1.454545
0.250000 0.156250 1.600000
0.250000 0.140625 1.777778
0.265625 0.218750 1.214286
0.218750 0.171875 1.272727
0.203125 0.234375 0.866667
Ratio median: 1.363636
2) Windows 8.1 v 6.3 bld 9600 (home test machine):
sp_blob_copy_1 sp_blob_copy_2 ratio
0.484375 0.359375 1.347826
0.375000 0.390625 0.960000
0.453125 0.406250 1.115385
0.406250 0.343750 1.181818
0.390625 0.359375 1.086957
0.421875 0.359375 1.173913
0.437500 0.406250 1.076923
0.421875 0.343750 1.227273
0.453125 0.296875 1.526316
0.468750 0.406250 1.153846
0.437500 0.437500 1.000000
Ratio median: 1.153846
"""
import os
import time
import psutil
import pytest
from firebird.qa import *
#--------------------------------------------------------------------
def median(lst):
n = len(lst)
s = sorted(lst)
return (sum(s[n//2-1:n//2+1])/2.0, s[n//2])[n % 2] if n else None
#--------------------------------------------------------------------
# Pre-defined alias for test DB in the QA_root/files/qa-databases.conf.
# This file (qa-databases.conf) must be copied manually to each testing
# FB home folder, with replacing databases.conf there:
#
REQUIRED_ALIAS = 'tmp_gh_7382_alias'
###########################
### S E T T I N G S ###
###########################
# How many times we call procedures:
N_MEASURES = 11
N_BLOB_FINAL_LEN = 32000
# How many iterations must be done:
N_COUNT_PER_MEASURE = 2000
# Maximal value for MEDIAN of ratios between CPU user time when comparison was made.
# On Windows 8.1 and Linux Debian this median ratio is ~1.25 ... 1.33
#
MAX_RATIO = 2 if os.name == 'nt' else 2
#######################################
init_script = \
f'''
set term ^;
create or alter procedure sp_blob_copy_1 (
n_len int
,n_cnt int
) as
declare b_src blob sub_type text character set unicode_fss;
declare b_tgt blob sub_type text character set utf8;
declare i int = 0;
begin
b_src = lpad('',n_len, 'a');
i = 0;
while (i < n_cnt) do
begin
-- copy_blob
b_tgt = b_src;
i = i + 1;
end
end
^
create or alter procedure sp_blob_copy_2 (
n_len int
,n_cnt int
) as
declare b_src blob sub_type text character set unicode_fss;
declare b_tgt blob sub_type text character set utf8;
declare i int = 0;
begin
while (i < n_len) do
begin
b_src = blob_append(b_src, 'a');
i = i + 1;
end
i = 0;
while (i < n_cnt) do
begin
-- copy_blob
b_tgt = b_src;
i = i + 1;
end
end
^
commit
^
'''
db = db_factory(filename = '#' + REQUIRED_ALIAS, init = init_script, charset = 'win1251' )
act = python_act('db')
expected_stdout = """
Duration ratio, median: acceptable
"""
@pytest.mark.version('>=5.0')
def test_1(act: Action, capsys):
with act.db.connect() as con:
cur=con.cursor()
cur.execute('select mon$server_pid as p from mon$attachments where mon$attachment_id = current_connection')
fb_pid = int(cur.fetchone()[0])
sp_time = {}
for i in range(0, N_MEASURES):
for sp_suffix in ('1','2'):
sp_name = f'sp_blob_copy_{sp_suffix}'
fb_info_init = psutil.Process(fb_pid).cpu_times()
cur.callproc( sp_name, (N_BLOB_FINAL_LEN, N_COUNT_PER_MEASURE,) )
#con.commit()
fb_info_curr = psutil.Process(fb_pid).cpu_times()
#sp_time[ sp_name, i ] = max(fb_info_curr.user+fb_info_curr.system - fb_info_init.user - fb_info_init.system, 0.000001)
sp_time[ sp_name, i ] = max(fb_info_curr.user - fb_info_init.user, 0.000001)
ratio_lst = []
for i in range(0, N_MEASURES):
ratio_lst.append( sp_time['sp_blob_copy_2',i] / sp_time['sp_blob_copy_1',i] )
median_ratio = median(ratio_lst)
print( 'Duration ratio, median: ' + ('acceptable' if median_ratio <= MAX_RATIO else '/* perf_issue_tag */ POOR: %s, more than threshold: %s' % ( '{:9g}'.format(median_ratio), '{:9g}'.format(MAX_RATIO) ) ) )
if median_ratio > MAX_RATIO:
print('Ratio statistics for %d measurements:' % N_MEASURES)
print('sp_blob_copy_1 sp_blob_copy_2 ratio')
for i in range(0, N_MEASURES):
print( '%14.6f %14.6f %14.6f' % (sp_time['sp_blob_copy_2',i], sp_time['sp_blob_copy_1',i], sp_time['sp_blob_copy_2',i] / sp_time['sp_blob_copy_1',i]) )
print('Ratio median: %12.6f' % median_ratio)
act.expected_stdout = expected_stdout
act.stdout = capsys.readouterr().out
assert act.clean_stdout == act.clean_expected_stdout