counted-float 1.0.5__tar.gz → 1.1.0__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.
- {counted_float-1.0.5 → counted_float-1.1.0}/PKG-INFO +36 -22
- {counted_float-1.0.5 → counted_float-1.1.0}/README.md +35 -21
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/counted_float/_counted_float_benchmark.py +4 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/_builtin_data.py +5 -8
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/_context_managers.py +5 -4
- counted_float-1.1.0/counted_float/_core/counting/_counted_float.py +205 -0
- counted_float-1.1.0/counted_float/_core/counting/_math_patching.py +270 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/config/_config.py +2 -1
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/config/_defaults.py +10 -2
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flop_weights.py +2 -2
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_geo_mean.py +1 -1
- {counted_float-1.0.5 → counted_float-1.1.0}/pyproject.toml +1 -1
- counted_float-1.0.5/counted_float/_core/counting/_counted_float.py +0 -402
- {counted_float-1.0.5 → counted_float-1.1.0}/.gitignore +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/LICENSE +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/_cli.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/counted_float/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_array_generator.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_flops_benchmark_suite.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/flops/_flops_micro_benchmark.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/micro/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/benchmarking/micro/_micro_benchmark.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/compatibility/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/compatibility/_numba.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/_global_counter.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/config/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_base.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flop_counts.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flop_type.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_meta_data.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_result.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_flops_benchmark_type.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_instruction_latencies.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/models/_micro_benchmark_result.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_cpu_freq.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_formatting.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_latency.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_missing_data.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_rounding.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/utils/_timer.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/benchmarking/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/config/__init__.py +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m1_github_actions.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m3_max_mbp16.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/apple_m3_mba15.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_2_neoverse_n1_ec2_m6g_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/benchmarks/aws_graviton_3_neoverse_v1_ec2_m7g_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_cortex_a76.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_cortex_x1.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_neoverse_n1.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v8_x/specs/arm_neoverse_v1.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/benchmarks/aws_graviton_4_neoverse_v2_ec2_m8g_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/benchmarks/azure_cobalt_100_neoverse_n2_github_actions.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_cortex_x2.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_cortex_x3.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_neoverse_n2.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_0/specs/arm_neoverse_v2.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_2/benchmarks/apple_m4_pro_mbp16.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_cortex_x4.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_cortex_x925.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/arm/v9_2/specs/arm_neoverse_v3.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2017_zen1/benchmarks/amd_ryzen_1700x.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2017_zen1/other/analysis_uops_info_zen1+.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_linux_github.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/benchmark/amd_epyc_7763_windows_github.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/other/analysis_agner_fog_r7_5800x.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2020_zen3/other/analysis_uops_info_zen3.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/benchmark/amd_epyc_9r14_ec2_m7a_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/analysis_agner_fog_r9_7900x.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/analysis_uops_info_zen4.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2022_zen4/other/specs_amd.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/benchmark/amd_epyc_9r45_ec2_m8a_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/other/analysis_agner_fog_r7_9800x3d.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/amd/2024_zen5/other/specs_amd.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8550U_windows.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/benchmarks/intel_i7_8700B_macos_github_actions.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_agner_fog_coffee_lake.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2017_coffee_lake_gen_8/other/analysis_uops_info_coffee_lake.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/benchmarks/intel_xeon_8375c_ice_lake_ec2_m6i_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_agner_fog_ice_lake.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_ice_lake.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2019_sunny_cove_gen_10/other/analysis_uops_info_tiger_lake.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/benchmarks/intel_xeon_8488c_sapphire_rapids_ec2_m7i_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/analysis_uops_info_alder_lake_p.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2021_golden_cove_gen_12/other/specs_intel.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/benchmarks/intel_xeon_8559c_emerald_rapids_ec2_i7i_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2022_raptor_cove_gen_13_14/other/specs_intel.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/benchmarks/intel_xeon_6975p_granite_rapids_ec2_m8i_xlarge.json +0 -0
- {counted_float-1.0.5 → counted_float-1.1.0}/counted_float/data/x86/intel/2023_redwood_cove_ultra_1/other/specs_intel.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: counted-float
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Count floating-point operations in Python code & benchmark relative flop costs.
|
|
5
5
|
Project-URL: Source, https://github.com/bertpl/counted-float
|
|
6
6
|
Project-URL: ChangeLog, https://github.com/bertpl/counted-float/blob/main/CHANGELOG.md
|
|
@@ -20,9 +20,9 @@ Requires-Dist: numba>=0.50; extra == 'numba'
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
|
|
22
22
|

|
|
23
|
-

|
|
24
|
+

|
|
25
|
+

|
|
26
26
|
|
|
27
27
|
# counted-float
|
|
28
28
|
|
|
@@ -61,10 +61,12 @@ The `CountedFloat` class is a subclass of `float` and is "contagious", meaning t
|
|
|
61
61
|
ensure results of math operations where at least one operand is a `CountedFloat` will also be a `CountedFloat`.
|
|
62
62
|
This way we ensure flop counting is a 'closed system'.
|
|
63
63
|
|
|
64
|
-
On top of this,
|
|
65
|
-
|
|
64
|
+
On top of this, `math` module functions that require counting (`sqrt`, `log2`, `pow`, ...) are
|
|
65
|
+
also instrumented: while a `FlopCountingContext` is active (see below), they are temporarily
|
|
66
|
+
replaced by counting equivalents. Outside such a context — including at plain `import` time —
|
|
67
|
+
the `math` module is left completely untouched.
|
|
66
68
|
|
|
67
|
-
**Example
|
|
69
|
+
**Example**:
|
|
68
70
|
|
|
69
71
|
```python
|
|
70
72
|
from counted_float import CountedFloat
|
|
@@ -78,18 +80,6 @@ is_float_1 = isinstance(cf, float) # True
|
|
|
78
80
|
is_float_2 = isinstance(result, float) # True
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
**Example 2**:
|
|
82
|
-
|
|
83
|
-
```python
|
|
84
|
-
import math
|
|
85
|
-
from counted_float import CountedFloat
|
|
86
|
-
|
|
87
|
-
cf1 = CountedFloat(0.81)
|
|
88
|
-
|
|
89
|
-
s = math.sqrt(cf1) # s = CountedFloat(0.9)
|
|
90
|
-
is_float = isinstance(s, float) # True
|
|
91
|
-
```
|
|
92
|
-
|
|
93
83
|
### The counting model: what gets counted and why
|
|
94
84
|
|
|
95
85
|
The counting model is a contract with two sides:
|
|
@@ -118,6 +108,13 @@ The library detects constants through two mechanisms, applying the same rule:
|
|
|
118
108
|
The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
|
|
119
109
|
at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
|
|
120
110
|
|
|
111
|
+
One more consequence of the context-scoped `math` patching: `math.*` calls participate in
|
|
112
|
+
counting (and in contagion) only while a `FlopCountingContext` is active. Operator-based
|
|
113
|
+
contagion (`+`, `*`, `**`, ...) works everywhere, but counts are meant to be read through a
|
|
114
|
+
context — so the practical rule is simply: run your measured algorithm inside one.
|
|
115
|
+
|
|
116
|
+
## 2.2. FLOP counting context managers
|
|
117
|
+
|
|
121
118
|
Once we use the `CountedFloat` class, we can use the available context managers to count the number of
|
|
122
119
|
flops performed by `CountedFloat` objects.
|
|
123
120
|
|
|
@@ -136,7 +133,24 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.ADD: 1}
|
|
|
136
133
|
counts.total_count() # 2
|
|
137
134
|
```
|
|
138
135
|
|
|
139
|
-
**Example 2**:
|
|
136
|
+
**Example 2**: _math module functions_
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import math
|
|
140
|
+
from counted_float import CountedFloat, FlopCountingContext
|
|
141
|
+
|
|
142
|
+
cf1 = CountedFloat(0.81)
|
|
143
|
+
|
|
144
|
+
with FlopCountingContext() as ctx:
|
|
145
|
+
s = math.sqrt(cf1) # s = CountedFloat(0.9)
|
|
146
|
+
|
|
147
|
+
counts = ctx.flop_counts() # {FlopType.SQRT: 1}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Note that `math.*` functions are only instrumented *inside* the context: outside it,
|
|
151
|
+
`math.sqrt(cf1)` returns a plain `float` and counts nothing.
|
|
152
|
+
|
|
153
|
+
**Example 3**: _pause counting 1_
|
|
140
154
|
|
|
141
155
|
```python
|
|
142
156
|
from counted_float import CountedFloat, FlopCountingContext
|
|
@@ -155,7 +169,7 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.SUB: 1}
|
|
|
155
169
|
counts.total_count() # 2
|
|
156
170
|
```
|
|
157
171
|
|
|
158
|
-
**Example
|
|
172
|
+
**Example 4**: _pause counting 2_
|
|
159
173
|
|
|
160
174
|
```python
|
|
161
175
|
from counted_float import CountedFloat, FlopCountingContext, PauseFlopCounting
|
|
@@ -546,7 +560,7 @@ CountedFloat is 37.3x slower than float
|
|
|
546
560
|
|
|
547
561
|
# Appendix A - Flop counting / analysis details
|
|
548
562
|
|
|
549
|
-
This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. For each flop type, you will find:
|
|
563
|
+
This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. All `math.*` entries below assume an active `FlopCountingContext` (outside one, the `math` module is not instrumented). For each flop type, you will find:
|
|
550
564
|
- Relevant scalar instructions for ARM (v8+) and x86 (SSE2+)
|
|
551
565
|
- Python operations that are counted for this flop type
|
|
552
566
|
- Python operations that are *not* counted for this flop type
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|

|
|
2
|
-

|
|
3
|
+

|
|
4
|
+

|
|
5
5
|
|
|
6
6
|
# counted-float
|
|
7
7
|
|
|
@@ -40,10 +40,12 @@ The `CountedFloat` class is a subclass of `float` and is "contagious", meaning t
|
|
|
40
40
|
ensure results of math operations where at least one operand is a `CountedFloat` will also be a `CountedFloat`.
|
|
41
41
|
This way we ensure flop counting is a 'closed system'.
|
|
42
42
|
|
|
43
|
-
On top of this,
|
|
44
|
-
|
|
43
|
+
On top of this, `math` module functions that require counting (`sqrt`, `log2`, `pow`, ...) are
|
|
44
|
+
also instrumented: while a `FlopCountingContext` is active (see below), they are temporarily
|
|
45
|
+
replaced by counting equivalents. Outside such a context — including at plain `import` time —
|
|
46
|
+
the `math` module is left completely untouched.
|
|
45
47
|
|
|
46
|
-
**Example
|
|
48
|
+
**Example**:
|
|
47
49
|
|
|
48
50
|
```python
|
|
49
51
|
from counted_float import CountedFloat
|
|
@@ -57,18 +59,6 @@ is_float_1 = isinstance(cf, float) # True
|
|
|
57
59
|
is_float_2 = isinstance(result, float) # True
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
**Example 2**:
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
import math
|
|
64
|
-
from counted_float import CountedFloat
|
|
65
|
-
|
|
66
|
-
cf1 = CountedFloat(0.81)
|
|
67
|
-
|
|
68
|
-
s = math.sqrt(cf1) # s = CountedFloat(0.9)
|
|
69
|
-
is_float = isinstance(s, float) # True
|
|
70
|
-
```
|
|
71
|
-
|
|
72
62
|
### The counting model: what gets counted and why
|
|
73
63
|
|
|
74
64
|
The counting model is a contract with two sides:
|
|
@@ -97,6 +87,13 @@ The library detects constants through two mechanisms, applying the same rule:
|
|
|
97
87
|
The flip side: an unwrapped runtime input is invisible to the counter — that is a wrapping error
|
|
98
88
|
at your algorithm's boundary, not something the library can detect. When in doubt, wrap.
|
|
99
89
|
|
|
90
|
+
One more consequence of the context-scoped `math` patching: `math.*` calls participate in
|
|
91
|
+
counting (and in contagion) only while a `FlopCountingContext` is active. Operator-based
|
|
92
|
+
contagion (`+`, `*`, `**`, ...) works everywhere, but counts are meant to be read through a
|
|
93
|
+
context — so the practical rule is simply: run your measured algorithm inside one.
|
|
94
|
+
|
|
95
|
+
## 2.2. FLOP counting context managers
|
|
96
|
+
|
|
100
97
|
Once we use the `CountedFloat` class, we can use the available context managers to count the number of
|
|
101
98
|
flops performed by `CountedFloat` objects.
|
|
102
99
|
|
|
@@ -115,7 +112,24 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.ADD: 1}
|
|
|
115
112
|
counts.total_count() # 2
|
|
116
113
|
```
|
|
117
114
|
|
|
118
|
-
**Example 2**:
|
|
115
|
+
**Example 2**: _math module functions_
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import math
|
|
119
|
+
from counted_float import CountedFloat, FlopCountingContext
|
|
120
|
+
|
|
121
|
+
cf1 = CountedFloat(0.81)
|
|
122
|
+
|
|
123
|
+
with FlopCountingContext() as ctx:
|
|
124
|
+
s = math.sqrt(cf1) # s = CountedFloat(0.9)
|
|
125
|
+
|
|
126
|
+
counts = ctx.flop_counts() # {FlopType.SQRT: 1}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Note that `math.*` functions are only instrumented *inside* the context: outside it,
|
|
130
|
+
`math.sqrt(cf1)` returns a plain `float` and counts nothing.
|
|
131
|
+
|
|
132
|
+
**Example 3**: _pause counting 1_
|
|
119
133
|
|
|
120
134
|
```python
|
|
121
135
|
from counted_float import CountedFloat, FlopCountingContext
|
|
@@ -134,7 +148,7 @@ counts = ctx.flop_counts() # {FlopType.MUL: 1, FlopType.SUB: 1}
|
|
|
134
148
|
counts.total_count() # 2
|
|
135
149
|
```
|
|
136
150
|
|
|
137
|
-
**Example
|
|
151
|
+
**Example 4**: _pause counting 2_
|
|
138
152
|
|
|
139
153
|
```python
|
|
140
154
|
from counted_float import CountedFloat, FlopCountingContext, PauseFlopCounting
|
|
@@ -525,7 +539,7 @@ CountedFloat is 37.3x slower than float
|
|
|
525
539
|
|
|
526
540
|
# Appendix A - Flop counting / analysis details
|
|
527
541
|
|
|
528
|
-
This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. For each flop type, you will find:
|
|
542
|
+
This appendix provides detailed information about how each floating-point operation (FLOP) type is counted and analyzed in the `counted-float` package. All `math.*` entries below assume an active `FlopCountingContext` (outside one, the `math` module is not instrumented). For each flop type, you will find:
|
|
529
543
|
- Relevant scalar instructions for ARM (v8+) and x86 (SSE2+)
|
|
530
544
|
- Python operations that are counted for this flop type
|
|
531
545
|
- Python operations that are *not* counted for this flop type
|
|
@@ -42,6 +42,8 @@ class BenchmarkFloat(MicroBenchmark):
|
|
|
42
42
|
# using standard float() arithmetic
|
|
43
43
|
a = -1e50
|
|
44
44
|
b = 1e50
|
|
45
|
+
# NOTE: fa/fb are never read (the loop re-evaluates fmid instead); they are kept
|
|
46
|
+
# on purpose so the kernel mimics a realistic bisection workload
|
|
45
47
|
fa = _zero_function(a)
|
|
46
48
|
fb = _zero_function(b)
|
|
47
49
|
while b - a > 1e-15:
|
|
@@ -70,6 +72,8 @@ class BenchmarkCountedFloat(MicroBenchmark):
|
|
|
70
72
|
# which will make sure all the remaining operations are also executed using CountedFloat arithmetic
|
|
71
73
|
a = CountedFloat(-1e50)
|
|
72
74
|
b = CountedFloat(1e50)
|
|
75
|
+
# NOTE: fa/fb are never read (the loop re-evaluates fmid instead); they are kept
|
|
76
|
+
# on purpose so the kernel mimics a realistic bisection workload
|
|
73
77
|
fa = _zero_function(a)
|
|
74
78
|
fb = _zero_function(b)
|
|
75
79
|
while b - a > 1e-15:
|
|
@@ -40,7 +40,7 @@ class BuiltInData:
|
|
|
40
40
|
raise ValueError(f"No built-in flop weights found for key_filter='{key_filter}'")
|
|
41
41
|
else:
|
|
42
42
|
nested_flop_weights_dict = _flat_to_nested_dict(flat_flop_weights_dict)
|
|
43
|
-
return
|
|
43
|
+
return _compute_nested_average_flop_weights(nested_flop_weights_dict)
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
46
|
def get_flop_weights_dict(cls, key_filter: str = "") -> dict[str, FlopWeights]:
|
|
@@ -86,11 +86,11 @@ class BuiltInData:
|
|
|
86
86
|
# =================================================================================================
|
|
87
87
|
# Utilities
|
|
88
88
|
# =================================================================================================
|
|
89
|
-
def
|
|
89
|
+
def _compute_nested_average_flop_weights(nested_flop_weights_dict: dict[str, dict | FlopWeights]) -> FlopWeights:
|
|
90
90
|
# make sure all values of the dict are FlopWeights instances
|
|
91
91
|
for key, value in nested_flop_weights_dict.items():
|
|
92
92
|
if isinstance(value, dict):
|
|
93
|
-
nested_flop_weights_dict[key] =
|
|
93
|
+
nested_flop_weights_dict[key] = _compute_nested_average_flop_weights(value)
|
|
94
94
|
|
|
95
95
|
# now we can average all FlopWeights instances
|
|
96
96
|
return FlopWeights.as_geo_mean(list(nested_flop_weights_dict.values()))
|
|
@@ -121,10 +121,6 @@ def _load_json_files_as_dict(resource_root) -> dict[str, str]:
|
|
|
121
121
|
'specs.x86.intel_core_i9_13900k'
|
|
122
122
|
"""
|
|
123
123
|
|
|
124
|
-
# allow both plain & recursive calls
|
|
125
|
-
# if resource_root is None:
|
|
126
|
-
# resource_root = files("counted_float._core.data")
|
|
127
|
-
|
|
128
124
|
# crawl entire folder structure
|
|
129
125
|
result = {}
|
|
130
126
|
for entry in resource_root.iterdir():
|
|
@@ -211,6 +207,7 @@ class FlopWeightsTreeView:
|
|
|
211
207
|
child.lst_is_leaf,
|
|
212
208
|
child.lst_tree_str,
|
|
213
209
|
child.lst_flop_weights,
|
|
210
|
+
strict=True,
|
|
214
211
|
)
|
|
215
212
|
):
|
|
216
213
|
self.lst_indent.append(1 + indent)
|
|
@@ -238,7 +235,6 @@ class FlopWeightsTreeView:
|
|
|
238
235
|
tree_width = 5 + max([len(line) for line in self.lst_tree_str])
|
|
239
236
|
col_width = 10
|
|
240
237
|
sorted_flop_types = self.lst_flop_weights[0].get_sorted_flop_types()
|
|
241
|
-
max_indent = max(self.lst_indent)
|
|
242
238
|
|
|
243
239
|
n_cols_per_block = max(1, int((console_width - tree_width) / col_width))
|
|
244
240
|
flop_types_per_block = [
|
|
@@ -260,6 +256,7 @@ class FlopWeightsTreeView:
|
|
|
260
256
|
self.lst_is_leaf,
|
|
261
257
|
self.lst_tree_str,
|
|
262
258
|
self.lst_flop_weights,
|
|
259
|
+
strict=True,
|
|
263
260
|
):
|
|
264
261
|
line = tree_str.ljust(tree_width)
|
|
265
262
|
for flop_type in flop_types:
|
{counted_float-1.0.5 → counted_float-1.1.0}/counted_float/_core/counting/_context_managers.py
RENAMED
|
@@ -4,6 +4,7 @@ as well as providing .pause() and .resume() methods to control flop counting.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from counted_float._core.counting._global_counter import GLOBAL_COUNTER
|
|
7
|
+
from counted_float._core.counting._math_patching import apply_math_patches, remove_math_patches
|
|
7
8
|
from counted_float._core.models import FlopCounts
|
|
8
9
|
|
|
9
10
|
|
|
@@ -15,10 +16,6 @@ class FlopCountingContext:
|
|
|
15
16
|
Context manager that can be used to count FLOP operations in a block of code. Only floating-point
|
|
16
17
|
operations of CountedFloat objects are counted. So make sure all math uses this type.
|
|
17
18
|
|
|
18
|
-
Flops need to be registered by either of the following:
|
|
19
|
-
- calls to register_flops(...)
|
|
20
|
-
- using CountedFloat() objects in the computations
|
|
21
|
-
|
|
22
19
|
LIMITATIONS:
|
|
23
20
|
- this context manager is not thread-safe
|
|
24
21
|
- not _all_ floating-point operations are counted, see the docs for more details.
|
|
@@ -71,11 +68,15 @@ class FlopCountingContext:
|
|
|
71
68
|
# Context manager interface
|
|
72
69
|
# -------------------------------------------------------------------------
|
|
73
70
|
def __enter__(self):
|
|
71
|
+
# patching the math module is tied to the with-block lifetime (not to pause()/resume(),
|
|
72
|
+
# which only control whether counts are registered)
|
|
73
|
+
apply_math_patches()
|
|
74
74
|
self.resume()
|
|
75
75
|
return self
|
|
76
76
|
|
|
77
77
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
78
78
|
self.pause()
|
|
79
|
+
remove_math_patches()
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
# =================================================================================================
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._global_counter import GLOBAL_COUNTER
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CountedFloat(float):
|
|
7
|
+
# -------------------------------------------------------------------------
|
|
8
|
+
# CONSTRUCTOR
|
|
9
|
+
# -------------------------------------------------------------------------
|
|
10
|
+
def __new__(cls, value: float | int):
|
|
11
|
+
if isinstance(value, int):
|
|
12
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
13
|
+
self = super().__new__(cls, float(value))
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return self.__repr__()
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"CountedFloat({super().__repr__()})"
|
|
21
|
+
|
|
22
|
+
def __hash__(self):
|
|
23
|
+
return super().__hash__()
|
|
24
|
+
|
|
25
|
+
# -------------------------------------------------------------------------
|
|
26
|
+
# OVERLOADED MATH OPERATIONS
|
|
27
|
+
# -------------------------------------------------------------------------
|
|
28
|
+
def __abs__(self) -> CountedFloat:
|
|
29
|
+
"""abs(x)"""
|
|
30
|
+
GLOBAL_COUNTER.incr_abs()
|
|
31
|
+
return CountedFloat(super().__abs__())
|
|
32
|
+
|
|
33
|
+
def __neg__(self) -> CountedFloat:
|
|
34
|
+
"""-x"""
|
|
35
|
+
GLOBAL_COUNTER.incr_minus()
|
|
36
|
+
return CountedFloat(super().__neg__())
|
|
37
|
+
|
|
38
|
+
def __eq__(self, other) -> bool:
|
|
39
|
+
"""x==other or other==x"""
|
|
40
|
+
if isinstance(other, int):
|
|
41
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
42
|
+
GLOBAL_COUNTER.incr_comp()
|
|
43
|
+
return super().__eq__(other)
|
|
44
|
+
|
|
45
|
+
def __ne__(self, other) -> bool:
|
|
46
|
+
"""x!=other or other!=x"""
|
|
47
|
+
if isinstance(other, int):
|
|
48
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
49
|
+
GLOBAL_COUNTER.incr_comp()
|
|
50
|
+
return super().__ne__(other)
|
|
51
|
+
|
|
52
|
+
def __lt__(self, other):
|
|
53
|
+
"""x<other"""
|
|
54
|
+
if isinstance(other, int):
|
|
55
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
56
|
+
GLOBAL_COUNTER.incr_comp()
|
|
57
|
+
return super().__lt__(other)
|
|
58
|
+
|
|
59
|
+
def __le__(self, other):
|
|
60
|
+
"""x<=other"""
|
|
61
|
+
if isinstance(other, int):
|
|
62
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
63
|
+
GLOBAL_COUNTER.incr_comp()
|
|
64
|
+
return super().__le__(other)
|
|
65
|
+
|
|
66
|
+
def __gt__(self, other):
|
|
67
|
+
"""x>other"""
|
|
68
|
+
if isinstance(other, int):
|
|
69
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
70
|
+
GLOBAL_COUNTER.incr_comp()
|
|
71
|
+
return super().__gt__(other)
|
|
72
|
+
|
|
73
|
+
def __ge__(self, other):
|
|
74
|
+
"""x>=other"""
|
|
75
|
+
if isinstance(other, int):
|
|
76
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
77
|
+
GLOBAL_COUNTER.incr_comp()
|
|
78
|
+
return super().__ge__(other)
|
|
79
|
+
|
|
80
|
+
def __round__(self, n=None) -> int:
|
|
81
|
+
"""
|
|
82
|
+
round(x, n)
|
|
83
|
+
n = None -> round to nearest integer and return int
|
|
84
|
+
n = 0 -> round to nearest integer and return float
|
|
85
|
+
n > 0 -> round to n decimal places and return float
|
|
86
|
+
"""
|
|
87
|
+
if n is None:
|
|
88
|
+
GLOBAL_COUNTER.incr_f2i() # will round and return int
|
|
89
|
+
else:
|
|
90
|
+
GLOBAL_COUNTER.incr_rnd() # will round and return float
|
|
91
|
+
|
|
92
|
+
return super().__round__(n)
|
|
93
|
+
|
|
94
|
+
def __floor__(self) -> int:
|
|
95
|
+
"""math.floor(x)"""
|
|
96
|
+
GLOBAL_COUNTER.incr_f2i()
|
|
97
|
+
return super().__floor__()
|
|
98
|
+
|
|
99
|
+
def __ceil__(self) -> int:
|
|
100
|
+
"""math.ceil(x)"""
|
|
101
|
+
GLOBAL_COUNTER.incr_f2i()
|
|
102
|
+
return super().__ceil__()
|
|
103
|
+
|
|
104
|
+
def __int__(self) -> int:
|
|
105
|
+
"""int(x)"""
|
|
106
|
+
GLOBAL_COUNTER.incr_f2i()
|
|
107
|
+
return super().__int__()
|
|
108
|
+
|
|
109
|
+
def __trunc__(self) -> int:
|
|
110
|
+
"""int(x)"""
|
|
111
|
+
GLOBAL_COUNTER.incr_f2i()
|
|
112
|
+
return super().__trunc__()
|
|
113
|
+
|
|
114
|
+
def __add__(self, other) -> CountedFloat:
|
|
115
|
+
"""x+other"""
|
|
116
|
+
GLOBAL_COUNTER.incr_add()
|
|
117
|
+
if isinstance(other, int):
|
|
118
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
119
|
+
return CountedFloat(super().__add__(other))
|
|
120
|
+
|
|
121
|
+
def __radd__(self, other) -> CountedFloat:
|
|
122
|
+
"""other+x"""
|
|
123
|
+
GLOBAL_COUNTER.incr_add()
|
|
124
|
+
if isinstance(other, int):
|
|
125
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
126
|
+
return CountedFloat(super().__radd__(other))
|
|
127
|
+
|
|
128
|
+
def __sub__(self, other) -> CountedFloat:
|
|
129
|
+
"""x-other"""
|
|
130
|
+
GLOBAL_COUNTER.incr_sub()
|
|
131
|
+
if isinstance(other, int):
|
|
132
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
133
|
+
return CountedFloat(super().__sub__(other))
|
|
134
|
+
|
|
135
|
+
def __rsub__(self, other) -> CountedFloat:
|
|
136
|
+
"""other-x"""
|
|
137
|
+
GLOBAL_COUNTER.incr_sub()
|
|
138
|
+
if isinstance(other, int):
|
|
139
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
140
|
+
return CountedFloat(super().__rsub__(other))
|
|
141
|
+
|
|
142
|
+
def __mul__(self, other) -> CountedFloat:
|
|
143
|
+
"""x*other or other*x"""
|
|
144
|
+
GLOBAL_COUNTER.incr_mul()
|
|
145
|
+
if isinstance(other, int):
|
|
146
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
147
|
+
return CountedFloat(super().__mul__(other))
|
|
148
|
+
|
|
149
|
+
def __rmul__(self, other) -> CountedFloat:
|
|
150
|
+
"""other*x"""
|
|
151
|
+
GLOBAL_COUNTER.incr_mul()
|
|
152
|
+
if isinstance(other, int):
|
|
153
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
154
|
+
return CountedFloat(super().__rmul__(other))
|
|
155
|
+
|
|
156
|
+
def __truediv__(self, other) -> CountedFloat:
|
|
157
|
+
"""x/other"""
|
|
158
|
+
GLOBAL_COUNTER.incr_div()
|
|
159
|
+
if isinstance(other, int):
|
|
160
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
161
|
+
return CountedFloat(super().__truediv__(other))
|
|
162
|
+
|
|
163
|
+
def __rtruediv__(self, other) -> CountedFloat:
|
|
164
|
+
"""other/x"""
|
|
165
|
+
GLOBAL_COUNTER.incr_div()
|
|
166
|
+
if isinstance(other, int):
|
|
167
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
168
|
+
return CountedFloat(super().__rtruediv__(other))
|
|
169
|
+
|
|
170
|
+
def __pow__(self, other) -> CountedFloat:
|
|
171
|
+
"""
|
|
172
|
+
x**other
|
|
173
|
+
|
|
174
|
+
Counting heuristic: an `int` operand is taken as evidence of a hardcoded constant in the
|
|
175
|
+
source (ints don't fall out of floating-point computations), so `x**2` counts as MUL —
|
|
176
|
+
the strength reduction (x*x) a compiled port would apply. A float operand may just as well
|
|
177
|
+
be a runtime variable that happens to hold that value, where a port would compile a
|
|
178
|
+
generic pow, so `x**2.0` counts as POW.
|
|
179
|
+
"""
|
|
180
|
+
if isinstance(other, int) and other == 2:
|
|
181
|
+
GLOBAL_COUNTER.incr_mul() # x^2 = x*x
|
|
182
|
+
else:
|
|
183
|
+
if isinstance(other, int):
|
|
184
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
185
|
+
GLOBAL_COUNTER.incr_pow()
|
|
186
|
+
return CountedFloat(super().__pow__(other))
|
|
187
|
+
|
|
188
|
+
def __rpow__(self, other) -> CountedFloat:
|
|
189
|
+
"""
|
|
190
|
+
other**x
|
|
191
|
+
|
|
192
|
+
Same constant-detection heuristic as __pow__, applied to the base: an `int` base 2 or 10
|
|
193
|
+
is taken as a hardcoded constant, counting as EXP2 / EXP10 (the strength reduction a
|
|
194
|
+
compiled port would apply); a float base may be a runtime variable, so it counts as
|
|
195
|
+
generic POW.
|
|
196
|
+
"""
|
|
197
|
+
if isinstance(other, int) and other == 2:
|
|
198
|
+
GLOBAL_COUNTER.incr_exp2()
|
|
199
|
+
elif isinstance(other, int) and other == 10:
|
|
200
|
+
GLOBAL_COUNTER.incr_exp10()
|
|
201
|
+
else:
|
|
202
|
+
if isinstance(other, int):
|
|
203
|
+
GLOBAL_COUNTER.incr_i2f()
|
|
204
|
+
GLOBAL_COUNTER.incr_pow()
|
|
205
|
+
return CountedFloat(super().__rpow__(other))
|