Skip to content

Commit c9a8e54

Browse files
committed
Update README
1 parent 04af5f7 commit c9a8e54

1 file changed

Lines changed: 39 additions & 11 deletions

File tree

README.md

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,59 @@
11
# partdiff-py
22

3-
This is a C++ port of [`partdiff`](https://github.com/parcio/partdiff).
3+
This is a Python port of [`partdiff`](https://github.com/parcio/partdiff).
44

55
## Usage
66

7-
<!--
8-
Shorter example used because of bad performance.
9-
TODO: Select longer example if performance is improved.
10-
-->
11-
127
```shell
138
$ git clone https://github.com/felsenhower/partdiff-py.git
14-
$ cd partdiff-py
9+
$ cd partdiff-py/simple
1510
$ uv run main.py 1 2 100 1 2 5
1611
```
1712

13+
## Variants
14+
15+
The repository contains three variants:
16+
- `simple`: An intentionally naïve and straightforward implementation (simple but slow)
17+
- `np_vectorize`: An implementation that uses numpy's fast factorized math for the Jacobi method. For the Gauß-Seidel method, this is not possible[^1], so we're only having some minor simplifications here.
18+
- `numba`: An implementation where the main loop has been JIT-compiled with numba.
19+
20+
All variants above use some shared code that can be found in `partdiff_common`.
21+
22+
[^1]: In general, it is not possible to parallelize the Gauß-Seidel without some form of synchronization if bitwise accuracy is needed. MPI can be used to parallelize Gauß-Seidel efficiently which works well for large problem sizes.
23+
1824
## Correctness
1925

2026
This project uses [partdiff_tester](https://github.com/parcio/partdiff_tester) via CI to ensure that the output matches the reference implementation.
2127
It passes the correctness tests with `--strictness=4` (exact match).
2228

23-
We are currently using `--max-num-tests=10` because the performance is quite bad.
29+
Since the performance of some variants (especially `simple`) is not great, we run fewer tests here (e.g. only `interlines=0` for `simple`).
2430

2531
## Performance
2632

27-
Currently, `partdiff-py` is slower than the reference implementation by a factor of about 200.
33+
See the table below for a runtime comparison of the variants that has been created with the scripts inside the `benchmark` directory. The C reference implementation serves as a comparison.
34+
35+
For all benchmarks, the arguments `1 {1,2} 100 2 2 100` were used. Therefore, this only serves to give you a rough overview.
36+
37+
`runtime_internal` shows the runtime that partdiff measured (the `Calculation time` field in the output) and `runtime_total` shows the runtime measured with `time`.
38+
39+
All Python implementations below have a larger runtime in total than the reference implementation. Since all of the startup code (arg-parsing, matrix initialization) were written in a pythonic and straightforward way, this is not surprising. With that in mind, I will only look at the internally measured runtime below.
40+
41+
As expected, the naïve implementation (`simple`) performs very badly. Here, the reference implementation is roughly 100x faster.
42+
43+
Same goes for the `np_vectorize` version with the Gauß-Seidel method which is even slightly slower than `simple`. Although it's not surprising that this is the case (since it contains an extra function call), it _is_ surprising that this is adding over 3 seconds of runtime.
44+
45+
With the Jacobi method, the `np_vectorize` version is even faster than the reference implementation, thanks to numpy's optimized vectorized math.
46+
47+
Finally, the `numba` version shows a comparable performance to the reference implementation, being slightly faster for Jacobi and slightly slower for Gauß-Seidel.
2848

29-
This makes it unsuitable for real-world use cases.
49+
| variant | method | runtime_internal | runtime_total | runtime_internal_factor | runtime_total_factor |
50+
|----------------|-------------|--------------------|--------------------|-------------------------|----------------------|
51+
| `reference` | Gauß-Seidel | (0.563 ± 0.023) s | (0.567 ± 0.029) s | 100.00% | 100.00% |
52+
| `reference` | Jacobi | (0.490 ± 0.017) s | (0.493 ± 0.023) s | 100.00% | 100.00% |
53+
| `simple` | Gauß-Seidel | (51.817 ± 0.273) s | (52.107 ± 0.273) s | 9198.22% | 9195.29% |
54+
| `simple` | Jacobi | (52.287 ± 0.508) s | (52.250 ± 1.087) s | 10670.75% | 10591.22% |
55+
| `numba` | Gauß-Seidel | (0.703 ± 0.023) s | (1.150 ± 0.017) s | 124.85% | 202.94% |
56+
| `numba` | Jacobi | (0.417 ± 0.006) s | (0.860 ± 0.010) s | 85.03% | 174.32% |
57+
| `np_vectorize` | Gauß-Seidel | (55.177 ± 0.303) s | (55.467 ± 0.303) s | 9794.67% | 9788.24% |
58+
| `np_vectorize` | Jacobi | (0.213 ± 0.006) s | (0.497 ± 0.012) s | 43.54% | 100.68% |
3059

31-
This is probably largely due to the very naïvely implemented main loop. This can probably be improved by leaning more heavily on numpy features.

0 commit comments

Comments
 (0)