ssrjson-benchmark 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ssrjson-benchmark might be problematic. Click here for more details.

@@ -0,0 +1,122 @@
1
+ cmake_minimum_required(VERSION 3.18)
2
+ project(ssrjson_benchmark LANGUAGES C)
3
+ set(CMAKE_C_STANDARD 17)
4
+ set(CMAKE_CXX_STANDARD 17)
5
+
6
+ # ------------------------------------------------------------------------------
7
+ # Build Options for tests and docs
8
+
9
+ # ------------------------------------------------------------------------------
10
+ # Project Config
11
+ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
12
+ include(XcodeProperty)
13
+
14
+ # ------------------------------------------------------------------------------
15
+ # Search Python Package
16
+ find_package(Python3 COMPONENTS Development)
17
+
18
+ # check for python3
19
+ if((NOT Python3_INCLUDE_DIRS) OR(NOT Python3_LIBRARIES))
20
+ message(FATAL_ERROR "Python3 not found")
21
+ endif()
22
+
23
+ message("Python3_INCLUDE_DIRS = ${Python3_INCLUDE_DIRS}")
24
+ message("Python3_LIBRARIES = ${Python3_LIBRARIES}")
25
+ message("Python3_LIBRARY_DIRS = ${Python3_LIBRARY_DIRS}")
26
+
27
+ # ------------------------------------------------------------------------------
28
+ # Build Type
29
+ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
30
+ if(NOT CMAKE_BUILD_TYPE)
31
+ message(STATUS "No build type selected, default to: Release")
32
+ set(CMAKE_BUILD_TYPE Release)
33
+ endif()
34
+ endif()
35
+
36
+ # ------------------------------------------------------------------------------
37
+ # Global settings
38
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
39
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
40
+
41
+ # ------------------------------------------------------------------------------
42
+ # Libraries
43
+
44
+ # common definitions
45
+ add_library(commonBuild INTERFACE)
46
+ target_link_libraries(commonBuild INTERFACE ${Python3_LIBRARIES})
47
+ target_include_directories(commonBuild INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> ${Python3_INCLUDE_DIRS})
48
+ if(MSVC)
49
+ target_compile_options(commonBuild INTERFACE /FAcs)
50
+ endif()
51
+
52
+ message("CMAKE_C_COMPILER_ID = ${CMAKE_C_COMPILER_ID}")
53
+ message("CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
54
+ message("CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}")
55
+
56
+
57
+ # ------------------------------------------------------------------------------
58
+ # setting up ssrjson_benchmark
59
+
60
+ # set ssrjson_benchmark src files
61
+ set(SRC_FILES
62
+ src/benchmark.c
63
+ )
64
+
65
+ add_library(ssrjson_benchmark SHARED ${SRC_FILES})
66
+ target_link_libraries(ssrjson_benchmark PUBLIC ${Python3_LIBRARIES})
67
+ set_target_properties(ssrjson_benchmark PROPERTIES PREFIX "")
68
+ target_include_directories(ssrjson_benchmark PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> ${Python3_INCLUDE_DIRS})
69
+
70
+ # ------------------------------------------------------------------------------
71
+ if(XCODE)
72
+ set(SSRJSON_BENCHMARK_FLAGS)
73
+ list(APPEND SSRJSON_BENCHMARK_FLAGS "-Wall" "-Wextra" "-Werror" "-pedantic" "-pedantic-errors" "-Wno-psabi")
74
+
75
+ set_default_xcode_property(commonBuild)
76
+ set_xcode_deployment_version(commonBuild "10.13" "12.0" "12.0" "4.0")
77
+
78
+ set_xcode_property(commonBuild GCC_C_LANGUAGE_STANDARD "c17")
79
+ set_xcode_property(commonBuild CLANG_C_LANGUAGE_STANDARD "c17")
80
+
81
+ # set_xcode_property(commonBuild CLANG_CXX_LANGUAGE_STANDARD "c++98")
82
+ set_xcode_property(commonBuild OTHER_CFLAGS[variant=Debug] ${SSRJSON_BENCHMARK_FLAGS})
83
+ set_xcode_property(commonBuild OTHER_CFLAGS[variant=MinSizeRel] ${SSRJSON_BENCHMARK_FLAGS})
84
+ set_xcode_property(commonBuild OTHER_CFLAGS[variant=RelWithDebInfo] ${SSRJSON_BENCHMARK_FLAGS})
85
+ set_xcode_property(commonBuild OTHER_CFLAGS[variant=Release] ${SSRJSON_BENCHMARK_FLAGS})
86
+
87
+ elseif(MSVC)
88
+ set(SSRJSON_BENCHMARK_FLAGS)
89
+ list(APPEND SSRJSON_BENCHMARK_FLAGS "/utf-8")
90
+
91
+ target_compile_options(commonBuild INTERFACE ${SSRJSON_BENCHMARK_FLAGS})
92
+
93
+ elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang|Intel")
94
+ set(SSRJSON_BENCHMARK_FLAGS)
95
+
96
+ if(CMAKE_C_COMPILER_ID MATCHES Clang)
97
+ # https://github.com/llvm/llvm-project/issues/63963
98
+ list(APPEND SSRJSON_BENCHMARK_FLAGS "-Wno-constant-logical-operand")
99
+ endif()
100
+
101
+ if(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
102
+ message("Release mode with debug info, enabling maximal optimization and debug symbols")
103
+ target_compile_options(commonBuild INTERFACE -Werror -Wno-psabi -pedantic -pedantic-errors)
104
+ elseif(CMAKE_BUILD_TYPE MATCHES ".*Rel.*")
105
+ message("Release mode, enabling maximal optimization")
106
+ target_compile_options(commonBuild INTERFACE -Werror -Wno-psabi -pedantic -pedantic-errors)
107
+ else()
108
+ target_compile_options(commonBuild INTERFACE -O0 -Wno-psabi)
109
+
110
+ message("Debug mode, enabling debug symbols")
111
+ endif()
112
+
113
+ target_compile_options(commonBuild INTERFACE ${SSRJSON_BENCHMARK_FLAGS})
114
+ endif()
115
+
116
+ # ------------------------------------------------------------------------------
117
+ # Install
118
+ if(APPLE)
119
+ set_target_properties(ssrjson_benchmark PROPERTIES SUFFIX ".so")
120
+ endif(APPLE)
121
+
122
+ install(TARGETS ssrjson_benchmark LIBRARY DESTINATION .)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Eritque arcus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include CMakeLists.txt
3
+ recursive-include src *
4
+ recursive-include cmake *
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: ssrjson_benchmark
3
+ Version: 0.0.1
4
+ License-File: LICENSE
5
+ Dynamic: license-file
@@ -0,0 +1,44 @@
1
+ # ssrJSON-benchmark
2
+
3
+ The [ssrJSON](https://github.com/Antares0982/ssrjson) benchmark repository.
4
+
5
+ ## Benchmark Results
6
+
7
+ The benchmark results can be found in [results](results). Contributing your benchmark result is welcomed.
8
+
9
+ Quick jump for
10
+
11
+ * [x86-64-v2, SSE4.2](results/SSE4.2)
12
+ * [x86-64-v3, AVX2](results/AVX2)
13
+ * [x86-64-v4, AVX512](results/AVX512)
14
+
15
+ ## Usage
16
+
17
+ To generate a benchmark report, you need to install `ssrJSON` either by fetched [PyPi](https://pypi.org/project/ssrjson/) or built from [source](https://github.com/Antares0982/ssrjson), and toolkit(`ssrjson_benchmark`) from this repo by:
18
+
19
+ ```bash
20
+ python -m build
21
+ pip install dist/*.whl
22
+ ```
23
+
24
+ Then run the benchmark script:
25
+
26
+ ```bash
27
+ python benchmark.py
28
+ ```
29
+
30
+ ## Benchmark options
31
+
32
+ * `-m` output in Markdown instead of PDF.
33
+ * `-f <json_path>` used exists benchmark json result.
34
+ * `--process-bytes <bytes_num>` Total process bytes per test, default 1e8.
35
+
36
+ ## Notes
37
+
38
+ * This repository conducts benchmarking using json, orjson, and ssrJSON. The `dumps` benchmark produces str objects, comparing three operations: `json.dumps`, `orjson.dumps` followed by decode, and `ssrjson.dumps`. The `dumps_to_bytes` benchmark produces bytes objects, comparing three functions: `json.dumps` followed by encode, `orjson.dumps`, and `ssrjson.dumps_to_bytes`.
39
+ * The ssrJSON built with the `BUILD_BENCHMARK` option includes several additional C functions specifically designed for executing benchmarks. These functions utilize high-precision timing APIs, and within the loop, only the time spent on the actual `PyObject_Call` invocations is measured.
40
+ * When orjson handles non-ASCII strings, if the cache of the `PyUnicodeObject`’s UTF-8 representation does not exist, it invokes the `PyUnicode_AsUTF8AndSize` function to obtain the UTF-8 encoding. This function then caches the UTF-8 representation within the `PyUnicodeObject`. If the same `PyUnicodeObject` undergoes repeated encode-decode operations, subsequent calls after the initial one will execute more quickly due to this caching. However, in real-world production scenarios, it is uncommon to perform JSON encode-decode repeatedly on the exact same string object; even identical strings are unlikely to be the same object instance. To achieve benchmark results that better reflect practical use cases, we employ `ssrjson.run_unicode_accumulate_benchmark` and `benchmark_invalidate_dump_cache` functions, which ensure that new `PyUnicodeObject`s are different for each input every time.
41
+
42
+ * The performance of JSON encoding is primarily constrained by the speed of writing to the buffer, whereas decoding performance is mainly limited by the frequent invocation of CPython interfaces for object creation. During decoding, both ssrJSON and orjson employ short key caching to reduce the number of object creations, and this caching mechanism is global in both cases. As a result, decoding benchmark tests may not accurately reflect the conditions encountered in real-world production environments.
43
+
44
+ * The files simple_object.json and simple_object_zh.json do not represent real-world data; they are solely used to compare the performance of the fast path. Therefore, the benchmark results should not be interpreted as indicative of actual performance.
@@ -0,0 +1,134 @@
1
+ # Copyright (C) 2019 Yaoyuan <ibireme@gmail.com>.
2
+ # Released under the MIT License:
3
+ # https://github.com/ibireme/yyjson/blob/master/LICENSE
4
+
5
+
6
+ # This module contains some macros for Xcode project.
7
+ # For example:
8
+ # if(XCODE)
9
+ # set_default_xcode_property(yyjson)
10
+ # set_xcode_deployment_version(yyjson "10.11" "9.0" "9.0" "2.0")
11
+ # set_xcode_property(yyjson GCC_C_LANGUAGE_STANDARD "c89")
12
+ # set_xcode_property(yyjson CLANG_CXX_LANGUAGE_STANDARD "c++98")
13
+ # endif()
14
+
15
+
16
+ # Set Xcode property to target
17
+ # For example: set_xcode_property(yyjson GCC_C_LANGUAGE_STANDARD "c89")
18
+ macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE)
19
+ set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE})
20
+ endmacro(set_xcode_property)
21
+
22
+
23
+ # Set default Xcode properties to target
24
+ # For example: set_default_xcode_property(yyjson)
25
+ macro(set_default_xcode_property TARGET)
26
+
27
+ # Standard
28
+ set_xcode_property(${TARGET} GCC_C_LANGUAGE_STANDARD "gnu99")
29
+ set_xcode_property(${TARGET} CLANG_CXX_LANGUAGE_STANDARD "gnu++11")
30
+ set_xcode_property(${TARGET} CLANG_CXX_LIBRARY "libc++")
31
+
32
+ # Compiler Flags
33
+ set_xcode_property(${TARGET} OTHER_CFLAGS[variant=Debug] " ")
34
+ set_xcode_property(${TARGET} OTHER_CFLAGS[variant=MinSizeRel] " ")
35
+ set_xcode_property(${TARGET} OTHER_CFLAGS[variant=RelWithDebInfo] " ")
36
+ set_xcode_property(${TARGET} OTHER_CFLAGS[variant=Release] " ")
37
+ set_xcode_property(${TARGET} OTHER_CPLUSPLUSFLAGS[variant=Debug] "$(OTHER_CFLAGS)")
38
+ set_xcode_property(${TARGET} OTHER_CPLUSPLUSFLAGS[variant=MinSizeRel] "$(OTHER_CFLAGS)")
39
+ set_xcode_property(${TARGET} OTHER_CPLUSPLUSFLAGS[variant=RelWithDebInfo] "$(OTHER_CFLAGS)")
40
+ set_xcode_property(${TARGET} OTHER_CPLUSPLUSFLAGS[variant=Release] "$(OTHER_CFLAGS)")
41
+
42
+ # Macros
43
+ set_xcode_property(${TARGET} GCC_PREPROCESSOR_DEFINITIONS[variant=Debug] "DEBUG=1")
44
+ set_xcode_property(${TARGET} GCC_PREPROCESSOR_DEFINITIONS[variant=MinSizeRel] "NDEBUG=1")
45
+ set_xcode_property(${TARGET} GCC_PREPROCESSOR_DEFINITIONS[variant=RelWithDebInfo] "NDEBUG=1")
46
+ set_xcode_property(${TARGET} GCC_PREPROCESSOR_DEFINITIONS[variant=Release] "NDEBUG=1")
47
+
48
+ # Architectures
49
+ set_xcode_property(${TARGET} ARCHS "$(ARCHS_STANDARD)")
50
+ set_xcode_property(${TARGET} ONLY_ACTIVE_ARCH[variant=Debug] "YES")
51
+ set_xcode_property(${TARGET} ONLY_ACTIVE_ARCH[variant=MinSizeRel] "NO")
52
+ set_xcode_property(${TARGET} ONLY_ACTIVE_ARCH[variant=RelWithDebInfo] "NO")
53
+ set_xcode_property(${TARGET} ONLY_ACTIVE_ARCH[variant=Release] "NO")
54
+ set_xcode_property(${TARGET} SDKROOT "macosx")
55
+
56
+ # Debug Information
57
+ set_xcode_property(${TARGET} DEBUG_INFORMATION_FORMAT[variant=Debug] "dwarf")
58
+ set_xcode_property(${TARGET} DEBUG_INFORMATION_FORMAT[variant=MinSizeRel] "dwarf-with-dsym")
59
+ set_xcode_property(${TARGET} DEBUG_INFORMATION_FORMAT[variant=RelWithDebInfo] "dwarf")
60
+ set_xcode_property(${TARGET} DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym")
61
+ set_xcode_property(${TARGET} GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Debug] "YES")
62
+ set_xcode_property(${TARGET} GCC_GENERATE_DEBUGGING_SYMBOLS[variant=MinSizeRel] "NO")
63
+ set_xcode_property(${TARGET} GCC_GENERATE_DEBUGGING_SYMBOLS[variant=RelWithDebInfo] "YES")
64
+ set_xcode_property(${TARGET} GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Release] "NO")
65
+ set_xcode_property(${TARGET} GCC_NO_COMMON_BLOCKS "YES")
66
+
67
+ # Common Warnings
68
+ set_xcode_property(${TARGET} CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING "YES")
69
+ set_xcode_property(${TARGET} CLANG_WARN_DOCUMENTATION_COMMENTS "YES")
70
+ set_xcode_property(${TARGET} CLANG_WARN_EMPTY_BODY "YES")
71
+ set_xcode_property(${TARGET} GCC_WARN_SHADOW "YES") ###
72
+ set_xcode_property(${TARGET} CLANG_WARN_BOOL_CONVERSION "YES")
73
+ set_xcode_property(${TARGET} CLANG_WARN_CONSTANT_CONVERSION "YES")
74
+ set_xcode_property(${TARGET} GCC_WARN_64_TO_32_BIT_CONVERSION "YES")
75
+ set_xcode_property(${TARGET} CLANG_WARN_ENUM_CONVERSION "YES")
76
+ set_xcode_property(${TARGET} CLANG_WARN_FLOAT_CONVERSION "YES") ###
77
+ set_xcode_property(${TARGET} CLANG_WARN_INT_CONVERSION "YES")
78
+ set_xcode_property(${TARGET} CLANG_WARN_NON_LITERAL_NULL_CONVERSION "YES")
79
+ set_xcode_property(${TARGET} CLANG_WARN_INFINITE_RECURSION "YES")
80
+ set_xcode_property(${TARGET} GCC_WARN_ABOUT_RETURN_TYPE "YES_ERROR")
81
+ set_xcode_property(${TARGET} GCC_WARN_ABOUT_MISSING_NEWLINE "YES") ###
82
+ set_xcode_property(${TARGET} CLANG_WARN_ASSIGN_ENUM "YES") ###
83
+ set_xcode_property(${TARGET} CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER "YES")
84
+ set_xcode_property(${TARGET} GCC_WARN_SIGN_COMPARE "YES") ###
85
+ set_xcode_property(${TARGET} CLANG_WARN_STRICT_PROTOTYPES "YES")
86
+ set_xcode_property(${TARGET} CLANG_WARN_COMMA "YES")
87
+ set_xcode_property(${TARGET} CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION "YES") ###
88
+ set_xcode_property(${TARGET} CLANG_WARN_UNGUARDED_AVAILABILITY "YES_AGGRESSIVE")
89
+ set_xcode_property(${TARGET} GCC_WARN_UNINITIALIZED_AUTOS "YES_AGGRESSIVE")
90
+ set_xcode_property(${TARGET} CLANG_WARN_UNREACHABLE_CODE "YES")
91
+ set_xcode_property(${TARGET} GCC_WARN_UNUSED_FUNCTION "YES")
92
+ set_xcode_property(${TARGET} GCC_WARN_UNUSED_VALUE "YES")
93
+ set_xcode_property(${TARGET} GCC_WARN_UNUSED_VARIABLE "YES") ###
94
+
95
+ # C++ Warnings
96
+ set_xcode_property(${TARGET} CLANG_WARN_RANGE_LOOP_ANALYSIS "YES")
97
+ set_xcode_property(${TARGET} CLANG_WARN_SUSPICIOUS_MOVE "YES")
98
+
99
+ # ObjC Warnings
100
+ set_xcode_property(${TARGET} CLANG_WARN_DIRECT_OBJC_ISA_USAGE "YES_ERROR")
101
+ set_xcode_property(${TARGET} CLANG_WARN__DUPLICATE_METHOD_MATCH "YES")
102
+ set_xcode_property(${TARGET} CLANG_WARN_OBJC_LITERAL_CONVERSION "YES")
103
+ set_xcode_property(${TARGET} CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS "YES")
104
+ set_xcode_property(${TARGET} GCC_WARN_UNDECLARED_SELECTOR "YES")
105
+ set_xcode_property(${TARGET} CLANG_WARN_OBJC_ROOT_CLASS "YES_ERROR")
106
+ set_xcode_property(${TARGET} CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF "YES")
107
+
108
+ # ObjC Options
109
+ set_xcode_property(${TARGET} CLANG_ENABLE_OBJC_ARC "YES")
110
+ set_xcode_property(${TARGET} CLANG_ENABLE_OBJC_WEAK "YES")
111
+ set_xcode_property(${TARGET} ENABLE_NS_ASSERTIONS[variant=Debug] "YES")
112
+ set_xcode_property(${TARGET} ENABLE_NS_ASSERTIONS[variant=MinSizeRel] "NO")
113
+ set_xcode_property(${TARGET} ENABLE_NS_ASSERTIONS[variant=RelWithDebInfo] "NO")
114
+ set_xcode_property(${TARGET} ENABLE_NS_ASSERTIONS[variant=Release] "NO")
115
+
116
+ endmacro(set_default_xcode_property)
117
+
118
+
119
+ # Set Xcode deployment version (macOS, iOS, tvOS, watchOS)
120
+ # For example: set_xcode_deployment_version(some_target "10.11" "9.0" "9.0" "2.0")
121
+ macro(set_xcode_deployment_version TARGET macOS iOS tvOS watchOS)
122
+ set_xcode_property(${TARGET} MACOSX_DEPLOYMENT_TARGET ${macOS})
123
+ set_xcode_property(${TARGET} IPHONEOS_DEPLOYMENT_TARGET ${iOS})
124
+ set_xcode_property(${TARGET} TVOS_DEPLOYMENT_TARGET ${tvOS})
125
+ set_xcode_property(${TARGET} WATCHOS_DEPLOYMENT_TARGET ${watchOS})
126
+ endmacro(set_xcode_deployment_version)
127
+
128
+
129
+ # Set Xcode language standard (C, CXX)
130
+ # For example: set_xcode_language_standard(some_target "gnu11" "gnu++17")
131
+ macro(set_xcode_language_standard TARGET C CXX)
132
+ set_xcode_property(${TARGET} GCC_C_LANGUAGE_STANDARD ${C})
133
+ set_xcode_property(${TARGET} CLANG_CXX_LANGUAGE_STANDARD ${CXX})
134
+ endmacro(set_xcode_language_standard)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,118 @@
1
+ # Copyright (c) 2025 Antares <antares0982@gmail.com>
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ import os
22
+ import shutil
23
+ import subprocess
24
+
25
+ from setuptools import Extension, setup
26
+ from setuptools.command.build_ext import build_ext
27
+ from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
28
+
29
+ # this is only for publishing
30
+
31
+
32
+ def check_version(version_str: str):
33
+ l = version_str.split(".")
34
+ for val in l:
35
+ if not val.isdigit():
36
+ raise ValueError(f"Invalid version string: {version_str}")
37
+
38
+
39
+ def find_version(src_file_content: str):
40
+ # find macro SSRJSON_BENCHMARK_VERSION
41
+ prefix = "#define SSRJSON_BENCHMARK_VERSION"
42
+ for line in src_file_content.splitlines():
43
+ if line.startswith(prefix):
44
+ version = line[len(prefix) :].strip()[1:-1]
45
+ check_version(version)
46
+ return version
47
+ raise RuntimeError("Cannot find SSRJSON_BENCHMARK_VERSION in source file")
48
+
49
+
50
+ with open("./src/benchmark.c", "r", encoding="utf-8") as f:
51
+ version_string = find_version(f.read())
52
+
53
+
54
+ class CMakeBuild(build_ext):
55
+ def run(self):
56
+ build_dir = os.path.abspath("build")
57
+ if not os.path.exists(build_dir):
58
+ os.makedirs(build_dir)
59
+ #
60
+ if os.name == "nt":
61
+ cmake_cmd = [
62
+ "cmake",
63
+ "-DCMAKE_BUILD_TYPE=Release",
64
+ ".",
65
+ "-B",
66
+ "build",
67
+ ]
68
+ else:
69
+ cmake_cmd = [
70
+ "cmake",
71
+ "-DCMAKE_BUILD_TYPE=Release",
72
+ ".",
73
+ "-B",
74
+ "build",
75
+ ]
76
+ subprocess.check_call(cmake_cmd)
77
+ #
78
+ if os.name == "nt":
79
+ build_cmd = ["cmake", "--build", "build", "--config", "Release"]
80
+ else:
81
+ build_cmd = ["cmake", "--build", "build"]
82
+ subprocess.check_call(build_cmd)
83
+ #
84
+ if os.name == "nt":
85
+ built_filename = "Release/ssrjson_benchmark.dll"
86
+ target_filename = "ssrjson_benchmark.pyd"
87
+ else:
88
+ built_filename = "ssrjson_benchmark.so"
89
+ target_filename = built_filename
90
+ #
91
+ built_path = os.path.join(build_dir, built_filename)
92
+ if not os.path.exists(built_path):
93
+ raise RuntimeError(f"Built library not found: {built_path}")
94
+ #
95
+ target_dir = self.build_lib
96
+ if not os.path.exists(target_dir):
97
+ os.makedirs(target_dir)
98
+ #
99
+ target_path = os.path.join(target_dir, target_filename)
100
+ self.announce(f"Copying {built_path} to {target_path}")
101
+ print(f"Copying {built_path} to {target_path}")
102
+ shutil.copyfile(built_path, target_path)
103
+
104
+
105
+ setup(
106
+ name="ssrjson_benchmark",
107
+ version=version_string,
108
+ # packages=["ssrjson_benchmark"],
109
+ ext_modules=[
110
+ Extension(
111
+ "ssrjson_benchmark",
112
+ sources=[],
113
+ )
114
+ ],
115
+ cmdclass={
116
+ "build_ext": CMakeBuild,
117
+ }
118
+ )
@@ -0,0 +1,354 @@
1
+ /*==============================================================================
2
+ Copyright (c) 2025 Antares <antares0982@gmail.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
21
+ *============================================================================*/
22
+
23
+ #include <Python.h>
24
+ #include <stdbool.h>
25
+
26
+ #define SSRJSON_BENCHMARK_VERSION "0.0.1"
27
+
28
+ /** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */
29
+ #ifndef has_builtin
30
+ # ifdef __has_builtin
31
+ # define has_builtin(x) __has_builtin(x)
32
+ # else
33
+ # define has_builtin(x) 0
34
+ # endif
35
+ #endif
36
+
37
+ /** unlikely for compiler */
38
+ #ifndef unlikely
39
+ # if has_builtin(__builtin_expect)
40
+ # define unlikely(expr) __builtin_expect(!!(expr), 0)
41
+ # else
42
+ # define unlikely(expr) (expr)
43
+ # endif
44
+ #endif
45
+
46
+ typedef unsigned long long usize;
47
+ #if defined(_WIN32) || defined(_WIN64)
48
+ # include <windows.h>
49
+
50
+ inline LARGE_INTEGER _get_frequency(void) {
51
+ LARGE_INTEGER frequency;
52
+ QueryPerformanceFrequency(&frequency);
53
+ return frequency;
54
+ }
55
+
56
+ usize perf_counter(void) {
57
+ static LARGE_INTEGER *frequency = NULL;
58
+ if (!frequency) {
59
+ frequency = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER));
60
+ if (!frequency) {
61
+ return 0;
62
+ }
63
+ *frequency = _get_frequency();
64
+ }
65
+ LARGE_INTEGER counter;
66
+ QueryPerformanceCounter(&counter);
67
+ return (usize)((counter.QuadPart * 1000000000LL) / frequency->QuadPart);
68
+ }
69
+
70
+ #else
71
+ # include <time.h>
72
+
73
+ usize perf_counter(void) {
74
+ struct timespec ts;
75
+ clock_gettime(CLOCK_MONOTONIC, &ts);
76
+ return (usize)ts.tv_sec * 1000000000LL + (usize)ts.tv_nsec;
77
+ }
78
+
79
+ #endif
80
+
81
+ typedef struct PyUnicodeNewCallArg {
82
+ Py_ssize_t size;
83
+ int kind;
84
+ Py_UCS4 max_char;
85
+ bool valid;
86
+ } PyUnicodeNewCallArg;
87
+
88
+ PyObject *_copy_unicode(PyObject *unicode, PyUnicodeNewCallArg *call_arg) {
89
+ if (!call_arg->valid) {
90
+ // create copy of unicode object.
91
+ int kind = PyUnicode_KIND(unicode);
92
+ Py_UCS4 max_char;
93
+ if (kind == 4) {
94
+ max_char = 0x10ffff;
95
+ } else if (kind == 2) {
96
+ max_char = 0xffff;
97
+ } else if (PyUnicode_IS_ASCII(unicode)) {
98
+ max_char = 0x7f;
99
+ } else {
100
+ max_char = 0xff;
101
+ }
102
+ //
103
+ call_arg->size = PyUnicode_GET_LENGTH(unicode);
104
+ call_arg->kind = kind;
105
+ call_arg->max_char = max_char;
106
+ call_arg->valid = true;
107
+ }
108
+
109
+ PyObject *unicode_copy = PyUnicode_New(call_arg->size, call_arg->max_char);
110
+ memcpy(PyUnicode_DATA(unicode_copy), PyUnicode_DATA(unicode),
111
+ call_arg->size * call_arg->kind);
112
+ return unicode_copy;
113
+ }
114
+
115
+ PyObject *_parse_additional_args(PyObject *additional_args) {
116
+ Py_ssize_t new_args_count = 1;
117
+ if (additional_args) {
118
+ new_args_count += PyTuple_GET_SIZE(additional_args);
119
+ }
120
+ PyObject *new_args = PyTuple_New(new_args_count);
121
+ if (!new_args) {
122
+ return NULL;
123
+ }
124
+ if (additional_args) {
125
+ for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(additional_args); i++) {
126
+ PyObject *item = PyTuple_GET_ITEM(additional_args, i);
127
+ Py_INCREF(item);
128
+ PyTuple_SET_ITEM(new_args, i + 1, item);
129
+ }
130
+ }
131
+ return new_args;
132
+ }
133
+
134
+ PyObject *run_unicode_accumulate_benchmark(PyObject *self, PyObject *args,
135
+ PyObject *kwargs) {
136
+ PyObject *callable;
137
+ usize repeat;
138
+ PyObject *unicode;
139
+ PyObject *additional_args = NULL;
140
+ static const char *kwlist[] = {"func", "repeat", "unicode", "args", NULL};
141
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OKO|O", (char **)kwlist,
142
+ &callable, &repeat, &unicode,
143
+ &additional_args)) {
144
+ PyErr_SetString(PyExc_TypeError, "Invalid argument");
145
+ goto fail;
146
+ }
147
+ if (!PyCallable_Check(callable)) {
148
+ PyErr_SetString(PyExc_TypeError, "First argument must be callable");
149
+ goto fail;
150
+ }
151
+ if (!PyUnicode_Check(unicode)) {
152
+ PyErr_SetString(PyExc_TypeError, "Third argument must be unicode");
153
+ goto fail;
154
+ }
155
+ PyUnicodeNewCallArg call_arg;
156
+ call_arg.valid = false;
157
+ usize total = 0;
158
+ for (usize i = 0; i < repeat; i++) {
159
+ // create copy of unicode object.
160
+ PyObject *new_args = _parse_additional_args(additional_args);
161
+ if (!new_args)
162
+ goto fail;
163
+ PyObject *unicode_copy = _copy_unicode(unicode, &call_arg);
164
+ if (!unicode_copy) {
165
+ Py_DECREF(new_args);
166
+ goto fail;
167
+ }
168
+ PyTuple_SET_ITEM(new_args, 0, unicode_copy);
169
+ usize start = perf_counter();
170
+ PyObject *result = PyObject_Call(callable, new_args, NULL);
171
+ usize end = perf_counter();
172
+ assert(unicode_copy->ob_refcnt == 1);
173
+ Py_DECREF(new_args);
174
+ unicode_copy = NULL;
175
+ new_args = NULL;
176
+ if (unlikely(!result)) {
177
+ if (!PyErr_Occurred()) {
178
+ PyErr_SetString(PyExc_RuntimeError, "Failed to call callable");
179
+ }
180
+ goto fail;
181
+ } else {
182
+ Py_DECREF(result);
183
+ }
184
+ total += end - start;
185
+ }
186
+ return PyLong_FromUnsignedLongLong(total);
187
+ fail:;
188
+ return NULL;
189
+ }
190
+
191
+ PyObject *run_object_accumulate_benchmark(PyObject *self, PyObject *args,
192
+ PyObject *kwargs) {
193
+ PyObject *callable;
194
+ usize repeat;
195
+ PyObject *call_args;
196
+ static const char *kwlist[] = {"func", "repeat", "args", NULL};
197
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OKO", (char **)kwlist,
198
+ &callable, &repeat, &call_args)) {
199
+ PyErr_SetString(PyExc_TypeError, "Invalid argument");
200
+ goto fail;
201
+ }
202
+ if (!PyCallable_Check(callable)) {
203
+ PyErr_SetString(PyExc_TypeError, "First argument must be callable");
204
+ goto fail;
205
+ }
206
+ if (!PyTuple_Check(call_args)) {
207
+ PyErr_SetString(PyExc_TypeError, "Third argument must be tuple");
208
+ goto fail;
209
+ }
210
+ usize total = 0;
211
+ for (usize i = 0; i < repeat; i++) {
212
+ usize start = perf_counter();
213
+ PyObject *result = PyObject_Call(callable, call_args, NULL);
214
+ usize end = perf_counter();
215
+ if (unlikely(!result)) {
216
+ if (!PyErr_Occurred()) {
217
+ PyErr_SetString(PyExc_RuntimeError, "Failed to call callable");
218
+ }
219
+ goto fail;
220
+ } else {
221
+ Py_DECREF(result);
222
+ }
223
+ total += end - start;
224
+ }
225
+ return PyLong_FromUnsignedLongLong(total);
226
+ fail:;
227
+ return NULL;
228
+ }
229
+
230
+ PyObject *run_object_benchmark(PyObject *self, PyObject *args,
231
+ PyObject *kwargs) {
232
+ PyObject *callable;
233
+ PyObject *call_args;
234
+ static const char *kwlist[] = {"func", "args", NULL};
235
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", (char **)kwlist,
236
+ &callable, &call_args)) {
237
+ PyErr_SetString(PyExc_TypeError, "Invalid argument");
238
+ goto fail;
239
+ }
240
+ if (!PyCallable_Check(callable)) {
241
+ PyErr_SetString(PyExc_TypeError, "First argument must be callable");
242
+ goto fail;
243
+ }
244
+ if (!PyTuple_Check(call_args)) {
245
+ PyErr_SetString(PyExc_TypeError, "Second argument must be tuple");
246
+ goto fail;
247
+ }
248
+ usize start = perf_counter();
249
+ PyObject *result = PyObject_Call(callable, call_args, NULL);
250
+ usize end = perf_counter();
251
+ if (unlikely(!result)) {
252
+ if (!PyErr_Occurred()) {
253
+ PyErr_SetString(PyExc_RuntimeError, "Failed to call callable");
254
+ }
255
+ goto fail;
256
+ } else {
257
+ Py_DECREF(result);
258
+ }
259
+ usize total;
260
+ total = end - start;
261
+ return PyLong_FromUnsignedLongLong(total);
262
+ fail:;
263
+ return NULL;
264
+ }
265
+
266
+ PyObject *inspect_pyunicode(PyObject *self, PyObject *args, PyObject *kwargs) {
267
+ PyObject *unicode;
268
+ PyObject *t1 = NULL, *t2 = NULL, *t3 = NULL, *t4 = NULL;
269
+ static const char *kwlist[] = {"unicode", NULL};
270
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", (char **)kwlist,
271
+ &unicode)) {
272
+ goto fail;
273
+ }
274
+ if (!PyUnicode_Check(unicode)) {
275
+ PyErr_SetString(PyExc_TypeError, "First argument must be unicode");
276
+ goto fail;
277
+ }
278
+ PyASCIIObject *u = (PyASCIIObject *)unicode;
279
+ int length = u->length;
280
+ int kind = u->state.kind;
281
+ int ascii = u->state.ascii;
282
+ int interned = u->state.interned;
283
+ t1 = PyLong_FromLong(kind);
284
+ if (!t1)
285
+ goto fail;
286
+ t2 = PyLong_FromLong(kind * length);
287
+ if (!t2)
288
+ goto fail;
289
+ t3 = PyBool_FromLong(ascii);
290
+ if (!t3)
291
+ goto fail;
292
+ t4 = PyBool_FromLong(interned);
293
+ if (!t4)
294
+ goto fail;
295
+ PyObject *ret = PyTuple_New(4);
296
+ if (!ret)
297
+ goto fail;
298
+ PyTuple_SET_ITEM(ret, 0, t1);
299
+ PyTuple_SET_ITEM(ret, 1, t2);
300
+ PyTuple_SET_ITEM(ret, 2, t3);
301
+ PyTuple_SET_ITEM(ret, 3, t4);
302
+ return ret;
303
+
304
+ fail:;
305
+ Py_XDECREF(t1);
306
+ Py_XDECREF(t2);
307
+ Py_XDECREF(t3);
308
+ Py_XDECREF(t4);
309
+ return NULL;
310
+ }
311
+
312
+ static PyMethodDef ssrjson_benchmark_methods[] = {
313
+ {"run_unicode_accumulate_benchmark", (PyCFunction)run_unicode_accumulate_benchmark, METH_VARARGS | METH_KEYWORDS, "Benchmark."},
314
+ {"run_object_accumulate_benchmark", (PyCFunction)run_object_accumulate_benchmark, METH_VARARGS | METH_KEYWORDS, "Benchmark."},
315
+ {"run_object_benchmark", (PyCFunction)run_object_benchmark, METH_VARARGS | METH_KEYWORDS, "Benchmark."},
316
+ {"inspect_pyunicode", (PyCFunction)inspect_pyunicode, METH_VARARGS | METH_KEYWORDS, "Inspect PyUnicode."},
317
+ {NULL, NULL, 0, NULL} /* Sentinel */
318
+ };
319
+
320
+ static struct PyModuleDef moduledef = {
321
+ PyModuleDef_HEAD_INIT,
322
+ "ssrjson_benchmark", /* m_name */
323
+ 0, /* m_doc */
324
+ 0, /* m_size */
325
+ ssrjson_benchmark_methods, /* m_methods */
326
+ NULL, /* m_slots */
327
+ NULL, /* m_traverse */
328
+ NULL, /* m_clear */
329
+ NULL /* m_free */
330
+ };
331
+
332
+ PyMODINIT_FUNC PyInit_ssrjson_benchmark(void) {
333
+ PyObject *module;
334
+ // check if module already exists
335
+ if ((module = PyState_FindModule(&moduledef)) != NULL) {
336
+ Py_INCREF(module);
337
+ return module;
338
+ }
339
+ // create module
340
+ module = PyModule_Create(&moduledef);
341
+ if (module == NULL) {
342
+ return NULL;
343
+ }
344
+ // add version string
345
+ {
346
+ int err = PyModule_AddStringConstant(module, "__version__", SSRJSON_BENCHMARK_VERSION);
347
+ if (err) {
348
+ Py_DECREF(module);
349
+ return NULL;
350
+ }
351
+ }
352
+
353
+ return module;
354
+ }
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: ssrjson_benchmark
3
+ Version: 0.0.1
4
+ License-File: LICENSE
5
+ Dynamic: license-file
@@ -0,0 +1,11 @@
1
+ CMakeLists.txt
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ setup.py
6
+ cmake/XcodeProperty.cmake
7
+ src/benchmark.c
8
+ ssrjson_benchmark.egg-info/PKG-INFO
9
+ ssrjson_benchmark.egg-info/SOURCES.txt
10
+ ssrjson_benchmark.egg-info/dependency_links.txt
11
+ ssrjson_benchmark.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ ssrjson_benchmark