Repository: modin-project/modin

Issue:
Using asv tool for microbenchmarking
This tool is used by the pandas. 

The displayed information looks [convenient](https://pandas.pydata.org/speed/pandas/#/).

Main [page](https://asv.readthedocs.io/en/stable/index.html).


Discussion:

Resolving patch:
diff --git a/asv_bench/benchmarks/__init__.py b/asv_bench/benchmarks/__init__.py
new file mode 100644
--- /dev/null
+++ b/asv_bench/benchmarks/__init__.py
@@ -0,0 +1,14 @@
+# Licensed to Modin Development Team under one or more contributor license agreements.
+# See the NOTICE file distributed with this work for additional information regarding
+# copyright ownership.  The Modin Development Team licenses this file to you under the
+# Apache License, Version 2.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+"""Modin benchmarks"""
diff --git a/asv_bench/benchmarks/benchmarks.py b/asv_bench/benchmarks/benchmarks.py
new file mode 100644
--- /dev/null
+++ b/asv_bench/benchmarks/benchmarks.py
@@ -0,0 +1,138 @@
+# Licensed to Modin Development Team under one or more contributor license agreements.
+# See the NOTICE file distributed with this work for additional information regarding
+# copyright ownership.  The Modin Development Team licenses this file to you under the
+# Apache License, Version 2.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+import modin.pandas as pd
+from modin.config import CpuCount, TestDatasetSize
+from .utils import generate_dataframe, RAND_LOW, RAND_HIGH
+
+# define `MODIN_CPUS` env var to control the number of partitions
+# it should be defined before modin.pandas import
+pd.DEFAULT_NPARTITIONS = CpuCount.get()
+
+if TestDatasetSize.get() == "Big":
+    MERGE_DATA_SIZE = [
+        (5000, 5000, 5000, 5000),
+        (10, 1_000_000, 10, 1_000_000),
+        (1_000_000, 10, 1_000_000, 10),
+    ]
+    GROUPBY_DATA_SIZE = [
+        (5000, 5000),
+        (10, 1_000_000),
+        (1_000_000, 10),
+    ]
+else:
+    MERGE_DATA_SIZE = [
+        (2000, 100, 2000, 100),
+    ]
+    GROUPBY_DATA_SIZE = [
+        (2000, 100),
+    ]
+
+JOIN_DATA_SIZE = MERGE_DATA_SIZE
+ARITHMETIC_DATA_SIZE = GROUPBY_DATA_SIZE
+
+
+class TimeGroupBy:
+    param_names = ["impl", "data_type", "data_size"]
+    params = [
+        ["modin", "pandas"],
+        ["int"],
+        GROUPBY_DATA_SIZE,
+    ]
+
+    def setup(self, impl, data_type, data_size):
+        self.df = generate_dataframe(
+            impl, data_type, data_size[0], data_size[1], RAND_LOW, RAND_HIGH
+        )
+
+    def time_groupby_sum(self, impl, data_type, data_size):
+        self.df.groupby(by=self.df.columns[0]).sum()
+
+    def time_groupby_mean(self, impl, data_type, data_size):
+        self.df.groupby(by=self.df.columns[0]).mean()
+
+    def time_groupby_count(self, impl, data_type, data_size):
+        self.df.groupby(by=self.df.columns[0]).count()
+
+
+class TimeJoin:
+    param_names = ["impl", "data_type", "data_size", "how", "sort"]
+    params = [
+        ["modin", "pandas"],
+        ["int"],
+        JOIN_DATA_SIZE,
+        ["left", "right", "outer", "inner"],
+        [False, True],
+    ]
+
+    def setup(self, impl, data_type, data_size, how, sort):
+        self.df1 = generate_dataframe(
+            impl, data_type, data_size[0], data_size[1], RAND_LOW, RAND_HIGH
+        )
+        self.df2 = generate_dataframe(
+            impl, data_type, data_size[2], data_size[3], RAND_LOW, RAND_HIGH
+        )
+
+    def time_join(self, impl, data_type, data_size, how, sort):
+        self.df1.join(
+            self.df2, on=self.df1.columns[0], how=how, lsuffix="left_", sort=sort
+        )
+
+
+class TimeMerge:
+    param_names = ["impl", "data_type", "data_size", "how", "sort"]
+    params = [
+        ["modin", "pandas"],
+        ["int"],
+        MERGE_DATA_SIZE,
+        ["left", "right", "outer", "inner"],
+        [False, True],
+    ]
+
+    def setup(self, impl, data_type, data_size, how, sort):
+        self.df1 = generate_dataframe(
+            impl, data_type, data_size[0], data_size[1], RAND_LOW, RAND_HIGH
+        )
+        self.df2 = generate_dataframe(
+            impl, data_type, data_size[2], data_size[3], RAND_LOW, RAND_HIGH
+        )
+
+    def time_merge(self, impl, data_type, data_size, how, sort):
+        self.df1.merge(self.df2, on=self.df1.columns[0], how=how, sort=sort)
+
+
+class TimeArithmetic:
+    param_names = ["impl", "data_type", "data_size", "axis"]
+    params = [
+        ["modin", "pandas"],
+        ["int"],
+        ARITHMETIC_DATA_SIZE,
+        [0, 1],
+    ]
+
+    def setup(self, impl, data_type, data_size, axis):
+        self.df = generate_dataframe(
+            impl, data_type, data_size[0], data_size[1], RAND_LOW, RAND_HIGH
+        )
+
+    def time_sum(self, impl, data_type, data_size, axis):
+        self.df.sum(axis=axis)
+
+    def time_median(self, impl, data_type, data_size, axis):
+        self.df.median(axis=axis)
+
+    def time_nunique(self, impl, data_type, data_size, axis):
+        self.df.nunique(axis=axis)
+
+    def time_apply(self, impl, data_type, data_size, axis):
+        self.df.apply(lambda df: df.sum(), axis=axis)
diff --git a/asv_bench/benchmarks/utils.py b/asv_bench/benchmarks/utils.py
new file mode 100644
--- /dev/null
+++ b/asv_bench/benchmarks/utils.py
@@ -0,0 +1,95 @@
+# Licensed to Modin Development Team under one or more contributor license agreements.
+# See the NOTICE file distributed with this work for additional information regarding
+# copyright ownership.  The Modin Development Team licenses this file to you under the
+# Apache License, Version 2.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+import logging
+import modin.pandas as pd
+import pandas
+import numpy as np
+
+RAND_LOW = 0
+RAND_HIGH = 100
+random_state = np.random.RandomState(seed=42)
+
+
+class weakdict(dict):
+    __slots__ = ("__weakref__",)
+
+
+data_cache = dict()
+dataframes_cache = dict()
+
+
+def gen_int_data(ncols, nrows, rand_low, rand_high):
+    cache_key = ("int", ncols, nrows, rand_low, rand_high)
+    if cache_key in data_cache:
+        return data_cache[cache_key]
+
+    logging.info(
+        "Generating int data {} rows and {} columns [{}-{}]".format(
+            nrows, ncols, rand_low, rand_high
+        )
+    )
+    data = {
+        "col{}".format(i): random_state.randint(rand_low, rand_high, size=(nrows))
+        for i in range(ncols)
+    }
+    data_cache[cache_key] = weakdict(data)
+    return data
+
+
+def gen_str_int_data(ncols, nrows, rand_low, rand_high):
+    cache_key = ("str_int", ncols, nrows, rand_low, rand_high)
+    if cache_key in data_cache:
+        return data_cache[cache_key]
+
+    logging.info(
+        "Generating str_int data {} rows and {} columns [{}-{}]".format(
+            nrows, ncols, rand_low, rand_high
+        )
+    )
+    data = gen_int_data(ncols, nrows, rand_low, rand_high).copy()
+    data["gb_col"] = [
+        "str_{}".format(random_state.randint(rand_low, rand_high)) for i in range(nrows)
+    ]
+    data_cache[cache_key] = weakdict(data)
+    return data
+
+
+def gen_data(data_type, ncols, nrows, rand_low, rand_high):
+    if data_type == "int":
+        return gen_int_data(ncols, nrows, rand_low, rand_high)
+    elif data_type == "str_int":
+        return gen_str_int_data(ncols, nrows, rand_low, rand_high)
+    else:
+        assert False
+
+
+def generate_dataframe(impl, data_type, ncols, nrows, rand_low, rand_high):
+    cache_key = (impl, data_type, ncols, nrows, rand_low, rand_high)
+    if cache_key in dataframes_cache:
+        return dataframes_cache[cache_key]
+
+    logging.info(
+        "Allocating {} DataFrame {}: {} rows and {} columns [{}-{}]".format(
+            impl, data_type, nrows, ncols, rand_low, rand_high
+        )
+    )
+    data = gen_data(data_type, ncols, nrows, rand_low, rand_high)
+    if impl == "modin":
+        df = pd.DataFrame(data)
+    elif impl == "pandas":
+        df = pandas.DataFrame(data)
+    else:
+        assert False
+    dataframes_cache[cache_key] = df
+    return df


Test patch: