Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Python] Add clear method for builder reuse #8186

Merged
merged 6 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions python/flatbuffers/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ def __init__(self, initialSize=1024):
## @endcond
self.finished = False

def Clear(self) -> None:
## @cond FLATBUFFERS_INTERNAL
self.current_vtable = None
self.head = UOffsetTFlags.py_type(len(self.Bytes))
self.minalign = 1
self.objectEnd = None
self.vtables = {}
self.nested = False
self.forceDefaults = False
self.sharedStrings = {}
self.vectorNumElems = None
## @endcond
self.finished = False

def Output(self):
"""Return the portion of the buffer that has been used for writing data.

Expand Down
16 changes: 8 additions & 8 deletions tests/PythonTest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function run_tests() {
JYTHONPATH=${runtime_library_dir}:${gen_code_path} \
COMPARE_GENERATED_TO_GO=0 \
COMPARE_GENERATED_TO_JAVA=0 \
$1 py_test.py $2 $3 $4 $5
$1 py_test.py $2 $3 $4 $5 $6
if [ $1 = python3 ]; then
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=${runtime_library_dir}:${gen_code_path} \
Expand All @@ -52,12 +52,12 @@ function run_tests() {
}

# Run test suite with these interpreters. The arguments are benchmark counts.
run_tests python2.6 100 100 100 false
run_tests python2.7 100 100 100 false
run_tests python2.7 100 100 100 true
run_tests python3 100 100 100 false
run_tests python3 100 100 100 true
run_tests pypy 100 100 100 false
run_tests python2.6 100 100 100 100 false
run_tests python2.7 100 100 100 100 false
run_tests python2.7 100 100 100 100 true
run_tests python3 100 100 100 100 false
run_tests python3 100 100 100 100 true
run_tests pypy 100 100 100 100 false

# NOTE: We'd like to support python2.5 in the future.

Expand All @@ -77,7 +77,7 @@ if $(which coverage >/dev/null); then

PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=${runtime_library_dir}:${gen_code_path} \
coverage run --source=flatbuffers,MyGame py_test.py 0 0 0 false > /dev/null
coverage run --source=flatbuffers,MyGame py_test.py 0 0 0 0 false > /dev/null

echo
cov_result=`coverage report --omit="*flatbuffers/vendor*,*py_test*" \
Expand Down
68 changes: 60 additions & 8 deletions tests/py_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,10 +2017,10 @@ def test_some_floats(self):
])


def make_monster_from_generated_code(sizePrefix=False, file_identifier=None):
def make_monster_from_generated_code(b=None, sizePrefix=False, file_identifier=None):
""" Use generated code to build the example Monster. """

b = flatbuffers.Builder(0)
if b is None:
b = flatbuffers.Builder(0)
string = b.CreateString('MyMonster')
test1 = b.CreateString('test1')
test2 = b.CreateString('test2')
Expand Down Expand Up @@ -2767,6 +2767,43 @@ def test_nested_union_tables(self):
self.assertEqual(nestUnionDecodeTFromBuf2.data.test3.b, nestUnion.data.test3.b)


class TestBuilderClear(unittest.TestCase):

def test_consistency(self):
""" Checks if clear resets the state of the builder. """
b = flatbuffers.Builder(0)

# Add some data to the buffer
off1 = b.CreateString('a' * 1024)
want = b.Bytes[b.Head():]

# Reset the builder
b.Clear()

# Readd the same data into the buffer
off2 = b.CreateString('a' * 1024)
got = b.Bytes[b.Head():]

# Expect to get the same data into the buffer at the same offset
self.assertEqual(off1, off2)
self.assertEqual(want, got)

def test_repeated_clear_after_builder_reuse(self):
init_buf = None
init_off = None
b = flatbuffers.Builder(0)

for i in range(5):
buf, off = make_monster_from_generated_code(b)
b.Clear()

if i > 0:
self.assertEqual(init_buf, buf)
self.assertEqual(init_off, off)
else:
init_buf = buf
init_off = off

def CheckAgainstGoldDataGo():
try:
gen_buf, gen_off = make_monster_from_generated_code()
Expand Down Expand Up @@ -2912,6 +2949,18 @@ def BenchmarkMakeMonsterFromGeneratedCode(count, length):
(count, length, duration, rate, data_rate)))


def BenchmarkBuilderClear(count, length):
b = flatbuffers.Builder(length)
duration = timeit.timeit(stmt=lambda: make_monster_from_generated_code(b),
number=count)
rate = float(count) / duration
data = float(length * count) / float(1024 * 1024)
data_rate = data / float(duration)

print(('built %d %d-byte flatbuffers (reused buffer) in %.2fsec:'
' %.2f/sec, %.2fMB/sec' % (count, length, duration, rate, data_rate)))


def backward_compatible_run_tests(**kwargs):
if PY_VERSION < (2, 6):
sys.stderr.write('Python version less than 2.6 are not supported')
Expand Down Expand Up @@ -2940,20 +2989,20 @@ def backward_compatible_run_tests(**kwargs):
def main():
import os
import sys
if not len(sys.argv) == 5:
if not len(sys.argv) == 6:
sys.stderr.write('Usage: %s <benchmark vtable count> '
'<benchmark read count> <benchmark build count> '
'<is_onefile>\n' % sys.argv[0])
'<benchmark clear builder> <is_onefile>\n' % sys.argv[0])
sys.stderr.write(' Provide COMPARE_GENERATED_TO_GO=1 to check'
'for bytewise comparison to Go data.\n')
sys.stderr.write(' Provide COMPARE_GENERATED_TO_JAVA=1 to check'
'for bytewise comparison to Java data.\n')
sys.stderr.flush()
sys.exit(1)

kwargs = dict(argv=sys.argv[:-4])
kwargs = dict(argv=sys.argv[:-5])

create_namespace_shortcut(sys.argv[4].lower() == 'true')
create_namespace_shortcut(sys.argv[5].lower() == 'true')

# show whether numpy is present, as it changes the test logic:
try:
Expand All @@ -2978,6 +3027,7 @@ def main():
bench_vtable = int(sys.argv[1])
bench_traverse = int(sys.argv[2])
bench_build = int(sys.argv[3])
bench_clear = int(sys.argv[4])
if bench_vtable:
BenchmarkVtableDeduplication(bench_vtable)
if bench_traverse:
Expand All @@ -2986,7 +3036,9 @@ def main():
if bench_build:
buf, off = make_monster_from_generated_code()
BenchmarkMakeMonsterFromGeneratedCode(bench_build, len(buf))

if bench_clear:
buf, off = make_monster_from_generated_code()
BenchmarkBuilderClear(bench_build, len(buf))

if __name__ == '__main__':
main()