From dfee931deac8cc5024e6ee143ab22ad0f6a9a59f Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:02:03 +0100 Subject: [PATCH 1/6] [stdlib] Integrate `hint_trivial_type` in `List.__copyinit__` Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/benchmarks/collections/bench_list.mojo | 88 +++++++++++++++++++ stdlib/src/collections/list.mojo | 10 ++- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 stdlib/benchmarks/collections/bench_list.mojo diff --git a/stdlib/benchmarks/collections/bench_list.mojo b/stdlib/benchmarks/collections/bench_list.mojo new file mode 100644 index 0000000000..74394e6b4c --- /dev/null +++ b/stdlib/benchmarks/collections/bench_list.mojo @@ -0,0 +1,88 @@ +# ===----------------------------------------------------------------------=== # +# Copyright (c) 2024, Modular Inc. All rights reserved. +# +# Licensed under the Apache License v2.0 with LLVM Exceptions: +# https://llvm.org/LICENSE.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ===----------------------------------------------------------------------=== # +# RUN: %mojo-no-debug %s -t +# NOTE: to test changes on the current branch using run-benchmarks.sh, remove +# the -t flag. Remember to replace it again before pushing any code. + +from benchmark import Bench, BenchConfig, Bencher, BenchId, Unit, keep, run +from memory import UnsafePointer +from random import seed +from collections import List +from collections import Dict +from time import now + + +def benchmark_list_hint_trivial_type_int[length: Int, iterations: Int]() -> Int: + var start = now() + var stop = now() + alias size = length + + var items = List[Int, True]() + for i in range(size): + items.append(i) + + start = now() + for iter in range(iterations): + var items2 = items + keep(items2.data) + stop = now() + keep(items.data) + return stop - start + + +def benchmark_string_copyinit__[length: Int, iterations: Int]() -> Int: + var start = now() + var stop = now() + + var x: String = "" + for l in range(length): + x += str(l)[0] + + start = now() + for iter in range(iterations): + var y: String + String.__copyinit__(y, x) + keep(y._buffer.data) + stop = now() + keep(x._buffer.data) + return stop - start + + +def main(): + seed() + + alias iterations = 1 << 10 + + alias result_type = Dict[String, Int] + var results = Dict[String, result_type]() + results["list_hint_trivial_type"] = result_type() + results["string_copyinit"] = result_type() + + alias lengths = (1, 2, 4, 8, 16, 32, 128, 256, 512, 1024, 2048, 4096) + + @parameter + for i in range(len(lengths)): + alias length = lengths.get[i, Int]() + results["list_hint_trivial_type"][ + str(length) + ] = benchmark_list_hint_trivial_type_int[length, iterations]() + results["string_copyinit"][str(length)] = benchmark_string_copyinit__[ + length, iterations + ]() + + print("iterations: ", iterations) + for benchmark in results: + print(benchmark[]) + for result in results[benchmark[]]: + print("\t", result[], "\t", results[benchmark[]][result[]]) + print() diff --git a/stdlib/src/collections/list.mojo b/stdlib/src/collections/list.mojo index f0bc3d0c6a..7889fec316 100644 --- a/stdlib/src/collections/list.mojo +++ b/stdlib/src/collections/list.mojo @@ -206,8 +206,14 @@ struct List[T: CollectionElement, hint_trivial_type: Bool = False]( existing: The list to copy. """ self = Self(capacity=existing.capacity) - for i in range(len(existing)): - self.append(existing[i]) + + @parameter + if hint_trivial_type: + memcpy(self.data, existing.data, len(existing)) + self.size = existing.size + else: + for i in range(len(existing)): + self.append(existing[i]) fn __del__(owned self): """Destroy all elements in the list and free its memory.""" From ade1281d96f6cbe93ae98e59ba054f7c3cdabfa6 Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:10:19 +0100 Subject: [PATCH 2/6] Add tests Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/test/collections/test_list.mojo | 44 ++++++++++++++++++++++++ stdlib/test/collections/test_string.mojo | 21 +++++++++++ 2 files changed, 65 insertions(+) diff --git a/stdlib/test/collections/test_list.mojo b/stdlib/test/collections/test_list.mojo index 56dab6510b..228e8845e4 100644 --- a/stdlib/test/collections/test_list.mojo +++ b/stdlib/test/collections/test_list.mojo @@ -18,6 +18,7 @@ from sys.info import sizeof from memory import UnsafePointer, Span from test_utils import CopyCounter, MoveCounter from testing import assert_equal, assert_false, assert_raises, assert_true +from testing import assert_not_equal def test_mojo_issue_698(): @@ -935,6 +936,48 @@ def test_list_repr(): assert_equal(empty.__repr__(), "[]") +def test_copyinit_trivial_types[dt: DType, hint_trivial_type:Bool](): + alias sizes = (1, 2, 4, 8, 16, 32, 64, 128, 256, 512) + assert_equal(len(sizes), 10) + var test_current_size = 1 + + @parameter + for sizes_index in range(len(sizes)): + alias current_size = sizes.get[sizes_index, Int]() + x = List[Scalar[dt], hint_trivial_type]() + for i in range(current_size): + x.append(i) + y = x + assert_equal(test_current_size, current_size) + assert_equal(len(y), current_size) + assert_not_equal(int(x.data), int(y.data)) + for i in range(current_size): + assert_equal(i, x[i]) + assert_equal(y[i], x[i]) + test_current_size *= 2 + assert_equal(test_current_size, 1024) + + +def test_copyinit_trivial_types_dtypes(): + alias dtypes = ( + DType.int64, + DType.int32, + DType.float64, + DType.float32, + DType.uint8, + DType.int8, + DType.bool, + ) + var test_index_dtype = 0 + + @parameter + for index_dtype in range(len(dtypes)): + test_copyinit_trivial_types[dtypes.get[index_dtype, DType](), True]() + test_copyinit_trivial_types[dtypes.get[index_dtype, DType](), False]() + test_index_dtype += 1 + assert_equal(test_index_dtype, 7) + + # ===-------------------------------------------------------------------===# # main # ===-------------------------------------------------------------------===# @@ -974,3 +1017,4 @@ def main(): test_list_dtor() test_destructor_trivial_elements() test_list_repr() + test_copyinit_trivial_types_dtypes() diff --git a/stdlib/test/collections/test_string.mojo b/stdlib/test/collections/test_string.mojo index 4d9151b279..2b6920bfb7 100644 --- a/stdlib/test/collections/test_string.mojo +++ b/stdlib/test/collections/test_string.mojo @@ -1593,6 +1593,26 @@ def test_reserve(): assert_equal(s._buffer.capacity, 1) +def test_copyinit(): + alias sizes = (1, 2, 4, 8, 16, 32, 64, 128, 256, 512) + assert_equal(len(sizes), 10) + var test_current_size = 1 + + @parameter + for sizes_index in range(len(sizes)): + alias current_size = sizes.get[sizes_index, Int]() + x = String("") + for i in range(current_size): + x += str(i)[0] + y = x + assert_equal(x._buffer, y._buffer) + assert_equal(test_current_size, current_size) + assert_equal(len(y), current_size) + assert_not_equal(int(x._buffer.data), int(y._buffer.data)) + test_current_size *= 2 + assert_equal(test_current_size, 1024) + + def main(): test_constructors() test_copy() @@ -1649,3 +1669,4 @@ def main(): test_center() test_float_conversion() test_slice_contains() + test_copyinit() From f6cf2c7d33d5e14cc42fa552ca7ba4acf7318e0b Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:16:08 +0100 Subject: [PATCH 3/6] Fix: mojo format the changes Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/test/collections/test_list.mojo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/test/collections/test_list.mojo b/stdlib/test/collections/test_list.mojo index 228e8845e4..efd1892ac4 100644 --- a/stdlib/test/collections/test_list.mojo +++ b/stdlib/test/collections/test_list.mojo @@ -936,7 +936,7 @@ def test_list_repr(): assert_equal(empty.__repr__(), "[]") -def test_copyinit_trivial_types[dt: DType, hint_trivial_type:Bool](): +def test_copyinit_trivial_types[dt: DType, hint_trivial_type: Bool](): alias sizes = (1, 2, 4, 8, 16, 32, 64, 128, 256, 512) assert_equal(len(sizes), 10) var test_current_size = 1 From f08eea7b6cc03f030bb01f7bb9d2445024966e40 Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:14:38 +0100 Subject: [PATCH 4/6] [stdlib] Add parametrized `benchmarks` for `List[Scalar[DT], is_trivial].__copyinit__` Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/benchmarks/collections/bench_list.mojo | 123 +++++++++--------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/stdlib/benchmarks/collections/bench_list.mojo b/stdlib/benchmarks/collections/bench_list.mojo index 74394e6b4c..2e30dc6341 100644 --- a/stdlib/benchmarks/collections/bench_list.mojo +++ b/stdlib/benchmarks/collections/bench_list.mojo @@ -15,74 +15,81 @@ # the -t flag. Remember to replace it again before pushing any code. from benchmark import Bench, BenchConfig, Bencher, BenchId, Unit, keep, run -from memory import UnsafePointer from random import seed from collections import List -from collections import Dict -from time import now - - -def benchmark_list_hint_trivial_type_int[length: Int, iterations: Int]() -> Int: - var start = now() - var stop = now() - alias size = length - - var items = List[Int, True]() - for i in range(size): - items.append(i) - - start = now() - for iter in range(iterations): - var items2 = items - keep(items2.data) - stop = now() +from random import * + + +# ===-----------------------------------------------------------------------===# +# Benchmark Data +# ===-----------------------------------------------------------------------===# +fn make_list[ + size: Int, DT: DType, is_trivial: Bool +]() -> List[Scalar[DT], is_trivial]: + alias scalar_t = Scalar[DT] + var d = List[Scalar[DT], is_trivial](capacity=size) + rand[DT]( + d.unsafe_ptr(), + size, + min=scalar_t.MIN.cast[DType.float64](), + max=scalar_t.MAX.cast[DType.float64](), + ) + d.size = size + return d + + +# ===-----------------------------------------------------------------------===# +# Benchmark `List[DT, True].__copyinit__` +# ===-----------------------------------------------------------------------===# + + +@parameter +fn bench_list_copyinit[ + size: Int, DT: DType, is_trivial: Bool +](mut b: Bencher) raises: + var items = make_list[size, DT, is_trivial]() + var result = List[Scalar[DT], is_trivial]() + var res = 0 + + @always_inline + @parameter + fn call_fn() raises: + result = items + res += len(result) + keep(result.data) + keep(items.data) + + b.iter[call_fn]() + print(res) + keep(bool(items)) + keep(bool(result)) + keep(result.data) keep(items.data) - return stop - start - - -def benchmark_string_copyinit__[length: Int, iterations: Int]() -> Int: - var start = now() - var stop = now() - - var x: String = "" - for l in range(length): - x += str(l)[0] - - start = now() - for iter in range(iterations): - var y: String - String.__copyinit__(y, x) - keep(y._buffer.data) - stop = now() - keep(x._buffer.data) - return stop - start def main(): seed() - alias iterations = 1 << 10 - - alias result_type = Dict[String, Int] - var results = Dict[String, result_type]() - results["list_hint_trivial_type"] = result_type() - results["string_copyinit"] = result_type() - + var m = Bench( + BenchConfig( + num_repetitions=1, + max_runtime_secs=0.5, + min_runtime_secs=0.25, + min_warmuptime_secs=0, # 0.25 + ) + ) alias lengths = (1, 2, 4, 8, 16, 32, 128, 256, 512, 1024, 2048, 4096) @parameter for i in range(len(lengths)): alias length = lengths.get[i, Int]() - results["list_hint_trivial_type"][ - str(length) - ] = benchmark_list_hint_trivial_type_int[length, iterations]() - results["string_copyinit"][str(length)] = benchmark_string_copyinit__[ - length, iterations - ]() - - print("iterations: ", iterations) - for benchmark in results: - print(benchmark[]) - for result in results[benchmark[]]: - print("\t", result[], "\t", results[benchmark[]][result[]]) - print() + m.bench_function[bench_list_copyinit[length, DType.uint8, True]]( + BenchId("List[Scalar[DT], True].__copyinit__ [" + str(length) + "]") + ) + m.bench_function[bench_list_copyinit[length, DType.uint8, False]]( + BenchId( + "List[Scalar[DT], False].__copyinit__ [" + str(length) + "]" + ) + ) + + m.dump_report() From e16f07cdc5961e5e5ced213e490706733f41443f Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:54:14 +0100 Subject: [PATCH 5/6] Fix: remove `seed()` and add a `FIXME` for the `BenchConfig` values Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/benchmarks/collections/bench_list.mojo | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stdlib/benchmarks/collections/bench_list.mojo b/stdlib/benchmarks/collections/bench_list.mojo index 2e30dc6341..52d78a7b0e 100644 --- a/stdlib/benchmarks/collections/bench_list.mojo +++ b/stdlib/benchmarks/collections/bench_list.mojo @@ -68,14 +68,12 @@ fn bench_list_copyinit[ def main(): - seed() - var m = Bench( BenchConfig( num_repetitions=1, max_runtime_secs=0.5, min_runtime_secs=0.25, - min_warmuptime_secs=0, # 0.25 + min_warmuptime_secs=0, #FIXME: adjust the values ) ) alias lengths = (1, 2, 4, 8, 16, 32, 128, 256, 512, 1024, 2048, 4096) From 6cb64b4f3702cbe3f6fa05ae74f6ed562c721da0 Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:03:23 +0100 Subject: [PATCH 6/6] =?UTF-8?q?Fix:=20=F0=9F=A5=A7=20mojo=20format=20the?= =?UTF-8?q?=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: rd4com <144297616+rd4com@users.noreply.github.com> --- stdlib/benchmarks/collections/bench_list.mojo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/benchmarks/collections/bench_list.mojo b/stdlib/benchmarks/collections/bench_list.mojo index 52d78a7b0e..f0653903f1 100644 --- a/stdlib/benchmarks/collections/bench_list.mojo +++ b/stdlib/benchmarks/collections/bench_list.mojo @@ -73,7 +73,7 @@ def main(): num_repetitions=1, max_runtime_secs=0.5, min_runtime_secs=0.25, - min_warmuptime_secs=0, #FIXME: adjust the values + min_warmuptime_secs=0, # FIXME: adjust the values ) ) alias lengths = (1, 2, 4, 8, 16, 32, 128, 256, 512, 1024, 2048, 4096)