From d0a97df2d9775b42ffbdb6b6b22c816b5fc82be4 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 29 Jul 2024 10:45:41 +0100 Subject: [PATCH 01/20] create simple benchmark suite --- benchmark/Project.toml | 2 ++ benchmark/benchmarks.jl | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 benchmark/Project.toml create mode 100644 benchmark/benchmarks.jl diff --git a/benchmark/Project.toml b/benchmark/Project.toml new file mode 100644 index 00000000..05a4894b --- /dev/null +++ b/benchmark/Project.toml @@ -0,0 +1,2 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl new file mode 100644 index 00000000..c057a885 --- /dev/null +++ b/benchmark/benchmarks.jl @@ -0,0 +1,49 @@ +using PythonCall, BenchmarkTools + +const SUITE = BenchmarkGroup() + +function test_pydict_init() + random = pyimport("random").random + x = pydict() + for i in pyrange(1000) + x[pystr(i)] = i + random() + end + return x +end + +SUITE["pydict"]["init"] = @benchmarkable test_pydict_init() + +function test_pydict_pydel() + random = pyimport("random").random + x = pydict() + for i in pyrange(1000) + k = pystr(i) + r = random() + v = i + r + x[k] = v + pydel!(k) + pydel!(r) + pydel!(v) + pydel!(i) + end + return x +end + +SUITE["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() + +@generated function test_atpy(::Val{use_pydel}) where {use_pydel} + quote + @py begin + import random: random + x = {} + for i in range(1000) + x[str(i)] = i + random() + $(use_pydel ? :(@jl PythonCall.pydel!(i)) : :(nothing)) + end + x + end + end +end + +SUITE["@py"]["basic"] = @benchmarkable test_atpy(Val(false)) +SUITE["@py"]["pydel"] = @benchmarkable test_atpy(Val(true)) From 410e31cb8be95d336fa06372540c87f33198d421 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 29 Jul 2024 10:47:16 +0100 Subject: [PATCH 02/20] more hierarchy in benchmark --- benchmark/benchmarks.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index c057a885..48b7bd59 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -11,7 +11,7 @@ function test_pydict_init() return x end -SUITE["pydict"]["init"] = @benchmarkable test_pydict_init() +SUITE["julia"]["pydict"]["init"] = @benchmarkable test_pydict_init() function test_pydict_pydel() random = pyimport("random").random @@ -29,7 +29,7 @@ function test_pydict_pydel() return x end -SUITE["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() +SUITE["julia"]["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() @generated function test_atpy(::Val{use_pydel}) where {use_pydel} quote @@ -45,5 +45,5 @@ SUITE["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() end end -SUITE["@py"]["basic"] = @benchmarkable test_atpy(Val(false)) -SUITE["@py"]["pydel"] = @benchmarkable test_atpy(Val(true)) +SUITE["@py"]["pydict"]["init"] = @benchmarkable test_atpy(Val(false)) +SUITE["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) From cd1948981f3d650e891c4d5f802ca7911470a69e Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 29 Jul 2024 10:48:57 +0100 Subject: [PATCH 03/20] create AirspeedVelocity github action for benchmarks --- .github/workflows/benchmark_pr.yml | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/benchmark_pr.yml diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml new file mode 100644 index 00000000..4c695cda --- /dev/null +++ b/.github/workflows/benchmark_pr.yml @@ -0,0 +1,64 @@ +name: Benchmark a pull request + +on: + pull_request_target: + branches: + - master + +permissions: + pull-requests: write + +jobs: + generate_plots: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: "1.9" + - uses: julia-actions/cache@v1 + - name: Extract Package Name from Project.toml + id: extract-package-name + run: | + PACKAGE_NAME=$(grep "^name" Project.toml | sed 's/^name = "\(.*\)"$/\1/') + echo "::set-output name=package_name::$PACKAGE_NAME" + - name: Build AirspeedVelocity + env: + JULIA_NUM_THREADS: 2 + run: | + # Lightweight build step, as sometimes the runner runs out of memory: + julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.add(;url="https://github.com/MilesCranmer/AirspeedVelocity.jl.git")' + julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.build("AirspeedVelocity")' + - name: Add ~/.julia/bin to PATH + run: | + echo "$HOME/.julia/bin" >> $GITHUB_PATH + - name: Run benchmarks + run: | + echo $PATH + ls -l ~/.julia/bin + mkdir results + benchpkg ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --url=${{ github.event.repository.clone_url }} --bench-on="${{github.event.pull_request.head.sha}}" --output-dir=results/ --tune --exeflags="-O3 --threads=auto" + - name: Create markdown table from benchmarks + run: | + benchpkgtable ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --input-dir=results/ --ratio > table.md + echo '### Benchmark Results' > body.md + echo '' >> body.md + echo '' >> body.md + cat table.md >> body.md + echo '' >> body.md + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fcbenchmark + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark Results + + - name: Comment on PR + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.fcbenchmark.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: body.md + edit-mode: replace From 072423da05c59459d621749880278d3c5dc3ea38 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 29 Jul 2024 10:51:01 +0100 Subject: [PATCH 04/20] fix imports in benchmark --- benchmark/benchmarks.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 48b7bd59..94414caa 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -1,4 +1,6 @@ -using PythonCall, BenchmarkTools +using BenchmarkTools +using PythonCall +using PythonCall: pydel!, pyimport, pydict, pystr, pyrange const SUITE = BenchmarkGroup() From 5f61a9c2f162976f2fea9c3658612247c0609608 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Mon, 29 Jul 2024 19:00:49 +0900 Subject: [PATCH 05/20] fix default branch name in CI --- .github/workflows/benchmark_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index 4c695cda..4ad1b6a1 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -3,7 +3,7 @@ name: Benchmark a pull request on: pull_request_target: branches: - - master + - main permissions: pull-requests: write From fcc687abb7c7461b7eef983a55597c89496a72cb Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 16:23:08 +0100 Subject: [PATCH 06/20] benchmarks: more hierarchy --- benchmark/benchmarks.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 94414caa..860844d4 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -13,7 +13,7 @@ function test_pydict_init() return x end -SUITE["julia"]["pydict"]["init"] = @benchmarkable test_pydict_init() +SUITE["basic"]["julia"]["pydict"]["init"] = @benchmarkable test_pydict_init() function test_pydict_pydel() random = pyimport("random").random @@ -31,7 +31,7 @@ function test_pydict_pydel() return x end -SUITE["julia"]["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() +SUITE["basic"]["julia"]["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() @generated function test_atpy(::Val{use_pydel}) where {use_pydel} quote @@ -47,5 +47,5 @@ SUITE["julia"]["pydict"]["pydel"] = @benchmarkable test_pydict_pydel() end end -SUITE["@py"]["pydict"]["init"] = @benchmarkable test_atpy(Val(false)) -SUITE["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) +SUITE["basic"]["@py"]["pydict"]["init"] = @benchmarkable test_atpy(Val(false)) +SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) From 4f7858987626a1b04bb64570242b33d05a3bb4aa Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Sat, 13 Jul 2024 18:14:32 +0200 Subject: [PATCH 07/20] timing + benchmark --- gcbench.jl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 gcbench.jl diff --git a/gcbench.jl b/gcbench.jl new file mode 100644 index 00000000..a2e6ea1a --- /dev/null +++ b/gcbench.jl @@ -0,0 +1,30 @@ +using PythonCall, Test + +# https://github.com/JuliaCI/GCBenchmarks/blob/fc288c696381ebfdef8f002168addd0ec1b08e34/benches/serial/append/append.jl +macro gctime(ex) + quote + local prior = PythonCall.GC.SECONDS_SPENT_IN_GC[] + local ret = @timed $(esc(ex)) + Base.time_print(stdout, ret.time * 1e9, ret.gcstats.allocd, ret.gcstats.total_time, Base.gc_alloc_count(ret.gcstats); msg="Runtime") + local after = PythonCall.GC.SECONDS_SPENT_IN_GC[] + println(stdout) + Base.time_print(stdout, (after - prior) * 1e9; msg="Python GC time") + println(stdout) + ret.value + end +end + +function append_lots(iters=100 * 1024, size=1596) + v = pylist() + for i = 1:iters + v.append(pylist(rand(size))) + end + return v +end + +@time "Total" begin + @gctime append_lots() + @time "Next full GC" begin + GC.gc(true) + end +end From 397ef3f6476b95ec7e81b3c5ce710e254e6dd64c Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 16:28:11 +0100 Subject: [PATCH 08/20] benchmarks: include gcbench in runs --- benchmark/benchmarks.jl | 4 ++++ gcbench.jl => benchmark/gcbench.jl | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) rename gcbench.jl => benchmark/gcbench.jl (87%) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 860844d4..f4462883 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -49,3 +49,7 @@ end SUITE["basic"]["@py"]["pydict"]["init"] = @benchmarkable test_atpy(Val(false)) SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) + + +include("gcbench.jl") +SUITE["gc"]["append_and_full_gc"] = @benchmarkable GC.gc(true) setup=(append_lots()) diff --git a/gcbench.jl b/benchmark/gcbench.jl similarity index 87% rename from gcbench.jl rename to benchmark/gcbench.jl index a2e6ea1a..45e1496c 100644 --- a/gcbench.jl +++ b/benchmark/gcbench.jl @@ -21,10 +21,3 @@ function append_lots(iters=100 * 1024, size=1596) end return v end - -@time "Total" begin - @gctime append_lots() - @time "Next full GC" begin - GC.gc(true) - end -end From 37c89d7546f0ef6749190e9b1e9a8438497073bb Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 16:28:53 +0100 Subject: [PATCH 09/20] benchmarks: remove unused code --- benchmark/gcbench.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/benchmark/gcbench.jl b/benchmark/gcbench.jl index 45e1496c..88b7bf81 100644 --- a/benchmark/gcbench.jl +++ b/benchmark/gcbench.jl @@ -1,19 +1,5 @@ using PythonCall, Test -# https://github.com/JuliaCI/GCBenchmarks/blob/fc288c696381ebfdef8f002168addd0ec1b08e34/benches/serial/append/append.jl -macro gctime(ex) - quote - local prior = PythonCall.GC.SECONDS_SPENT_IN_GC[] - local ret = @timed $(esc(ex)) - Base.time_print(stdout, ret.time * 1e9, ret.gcstats.allocd, ret.gcstats.total_time, Base.gc_alloc_count(ret.gcstats); msg="Runtime") - local after = PythonCall.GC.SECONDS_SPENT_IN_GC[] - println(stdout) - Base.time_print(stdout, (after - prior) * 1e9; msg="Python GC time") - println(stdout) - ret.value - end -end - function append_lots(iters=100 * 1024, size=1596) v = pylist() for i = 1:iters From 5295cc7c71625d5bac0245eca42d7cf6ee70d456 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 16:30:36 +0100 Subject: [PATCH 10/20] benchmarks: modularize --- benchmark/benchmarks.jl | 2 ++ benchmark/gcbench.jl | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index f4462883..70d2a48e 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -52,4 +52,6 @@ SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) include("gcbench.jl") +using .GCBench: append_lots + SUITE["gc"]["append_and_full_gc"] = @benchmarkable GC.gc(true) setup=(append_lots()) diff --git a/benchmark/gcbench.jl b/benchmark/gcbench.jl index 88b7bf81..56f8fc05 100644 --- a/benchmark/gcbench.jl +++ b/benchmark/gcbench.jl @@ -1,4 +1,6 @@ -using PythonCall, Test +module GCBench + +using PythonCall function append_lots(iters=100 * 1024, size=1596) v = pylist() @@ -7,3 +9,5 @@ function append_lots(iters=100 * 1024, size=1596) end return v end + +end From 5d4e630a71ce43122c8c3d21db0b1b97c9ecea29 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 17:31:53 +0100 Subject: [PATCH 11/20] benchmarks: give more time to GC benchmark --- benchmark/benchmarks.jl | 6 +++++- benchmark/gcbench.jl | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 70d2a48e..db8b58cb 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -54,4 +54,8 @@ SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) include("gcbench.jl") using .GCBench: append_lots -SUITE["gc"]["append_and_full_gc"] = @benchmarkable GC.gc(true) setup=(append_lots()) +SUITE["gc"]["append_and_full_gc"] = @benchmarkable( + GC.gc(true), + setup=(GC.gc(true); append_lots(size=159)), + seconds=30, +) diff --git a/benchmark/gcbench.jl b/benchmark/gcbench.jl index 56f8fc05..d9e83070 100644 --- a/benchmark/gcbench.jl +++ b/benchmark/gcbench.jl @@ -2,7 +2,7 @@ module GCBench using PythonCall -function append_lots(iters=100 * 1024, size=1596) +function append_lots(; iters=100 * 1024, size=1596) v = pylist() for i = 1:iters v.append(pylist(rand(size))) From ae73e43f1fae4a422f788d748a6532eadb3bb569 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 2 Aug 2024 17:40:14 +0100 Subject: [PATCH 12/20] benchmarks: simplify naming scheme --- benchmark/benchmarks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index db8b58cb..47ce71ad 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -54,7 +54,7 @@ SUITE["basic"]["@py"]["pydict"]["pydel"] = @benchmarkable test_atpy(Val(true)) include("gcbench.jl") using .GCBench: append_lots -SUITE["gc"]["append_and_full_gc"] = @benchmarkable( +SUITE["gc"]["full"] = @benchmarkable( GC.gc(true), setup=(GC.gc(true); append_lots(size=159)), seconds=30, From 83ddd096ba9c57e4cce0efc2042935ef6a6d08e8 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sat, 3 Aug 2024 02:52:39 +0900 Subject: [PATCH 13/20] benchmarks: avoid issue of `tune`ing away from `evals=1` --- benchmark/benchmarks.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 47ce71ad..38bbfd07 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -58,4 +58,5 @@ SUITE["gc"]["full"] = @benchmarkable( GC.gc(true), setup=(GC.gc(true); append_lots(size=159)), seconds=30, + evals=1, ) From 96bd2280c9b3cf2e5011aa69ac9fb8a8753553a8 Mon Sep 17 00:00:00 2001 From: Christopher Doris Date: Wed, 28 May 2025 22:19:28 +0100 Subject: [PATCH 14/20] use getptr a lot less --- src/C/extras.jl | 29 +++--- src/Compat/pycall.jl | 2 +- src/Convert/ctypes.jl | 2 +- src/Convert/numpy.jl | 2 +- src/Convert/pyconvert.jl | 4 +- src/Convert/rules.jl | 4 +- src/Core/Py.jl | 11 ++- src/Core/builtins.jl | 181 +++++++++++++++++++------------------- src/Core/err.jl | 23 +++-- src/JlWrap/C.jl | 10 ++- src/JlWrap/base.jl | 18 ++-- src/JlWrap/io.jl | 4 +- src/JlWrap/objectarray.jl | 2 +- src/Wrap/PyArray.jl | 12 +-- 14 files changed, 155 insertions(+), 149 deletions(-) diff --git a/src/C/extras.jl b/src/C/extras.jl index bc8679df..cf46b6b8 100644 --- a/src/C/extras.jl +++ b/src/C/extras.jl @@ -1,23 +1,26 @@ -Py_Type(x::PyPtr) = PyPtr(UnsafePtr(x).type[!]) +asptr(x) = Base.unsafe_convert(PyPtr, x) -PyObject_Type(x::PyPtr) = (t = Py_Type(x); Py_IncRef(t); t) +Py_Type(x) = Base.GC.@preserve x PyPtr(UnsafePtr(asptr(x)).type[!]) -Py_TypeCheck(o::PyPtr, t::PyPtr) = PyType_IsSubtype(Py_Type(o), t) -Py_TypeCheckFast(o::PyPtr, f::Integer) = PyType_IsSubtypeFast(Py_Type(o), f) +PyObject_Type(x) = Base.GC.@preserve x (t = Py_Type(asptr(x)); Py_IncRef(t); t) -PyType_IsSubtypeFast(t::PyPtr, f::Integer) = - Cint(!iszero(UnsafePtr{PyTypeObject}(t).flags[] & f)) +Py_TypeCheck(o, t) = Base.GC.@preserve o t PyType_IsSubtype(Py_Type(asptr(o)), asptr(t)) +Py_TypeCheckFast(o, f::Integer) = Base.GC.@preserve o PyType_IsSubtypeFast(Py_Type(asptr(o)), f) -PyMemoryView_GET_BUFFER(m::PyPtr) = Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(m).view) +PyType_IsSubtypeFast(t, f::Integer) = + Base.GC.@preserve t Cint(!iszero(UnsafePtr{PyTypeObject}(asptr(t)).flags[] & f)) -PyType_CheckBuffer(t::PyPtr) = begin - p = UnsafePtr{PyTypeObject}(t).as_buffer[] +PyMemoryView_GET_BUFFER(m) = Base.GC.@preserve m Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(asptr(m)).view) + +PyType_CheckBuffer(t) = Base.GC.@preserve t begin + p = UnsafePtr{PyTypeObject}(asptr(t)).as_buffer[] return p != C_NULL && p.get[!] != C_NULL end -PyObject_CheckBuffer(o::PyPtr) = PyType_CheckBuffer(Py_Type(o)) +PyObject_CheckBuffer(o) = Base.GC.@preserve o PyType_CheckBuffer(Py_Type(asptr(o))) -PyObject_GetBuffer(o::PyPtr, b, flags) = begin +PyObject_GetBuffer(_o, b, flags) = Base.GC.@preserve _o begin + o = asptr(_o) p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[] if p == C_NULL || p.get[!] == C_NULL PyErr_SetString( @@ -61,8 +64,8 @@ function PyOS_RunInputHook() end end -function PySimpleObject_GetValue(::Type{T}, o::PyPtr) where {T} - UnsafePtr{PySimpleObject{T}}(o).value[!] +function PySimpleObject_GetValue(::Type{T}, o) where {T} + Base.GC.@preserve o UnsafePtr{PySimpleObject{T}}(asptr(o)).value[!] end # FAST REFCOUNTING diff --git a/src/Compat/pycall.jl b/src/Compat/pycall.jl index 8c06f5d3..2dbfc3d1 100644 --- a/src/Compat/pycall.jl +++ b/src/Compat/pycall.jl @@ -16,6 +16,6 @@ function init_pycall(PyCall::Module) end @eval function PyCall.PyObject(x::Py) C.CTX.matches_pycall::Bool || error($errmsg) - return $PyCall.PyObject($PyCall.PyPtr(incref(getptr(x)))) + return $PyCall.PyObject($PyCall.PyPtr(getptr(incref(x)))) end end diff --git a/src/Convert/ctypes.jl b/src/Convert/ctypes.jl index d081f563..2b5fe449 100644 --- a/src/Convert/ctypes.jl +++ b/src/Convert/ctypes.jl @@ -1,7 +1,7 @@ struct pyconvert_rule_ctypessimplevalue{R,S} <: Function end function (::pyconvert_rule_ctypessimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T} - ptr = Base.GC.@preserve x C.PySimpleObject_GetValue(Ptr{R}, getptr(x)) + ptr = C.PySimpleObject_GetValue(Ptr{R}, x) ans = unsafe_load(ptr) if SAFE pyconvert_return(convert(T, ans)) diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl index adb621cc..bd708e8a 100644 --- a/src/Convert/numpy.jl +++ b/src/Convert/numpy.jl @@ -1,7 +1,7 @@ struct pyconvert_rule_numpysimplevalue{R,S} <: Function end function (::pyconvert_rule_numpysimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T} - ans = Base.GC.@preserve x C.PySimpleObject_GetValue(R, getptr(x)) + ans = C.PySimpleObject_GetValue(R, x) if SAFE pyconvert_return(convert(T, ans)) else diff --git a/src/Convert/pyconvert.jl b/src/Convert/pyconvert.jl index c4061b1d..d61268de 100644 --- a/src/Convert/pyconvert.jl +++ b/src/Convert/pyconvert.jl @@ -238,7 +238,7 @@ function _pyconvert_get_rules(pytype::Py) end end for (t, x) in reverse(collect(zip(mro, xmro))) - if C.PyType_CheckBuffer(getptr(t)) + if C.PyType_CheckBuffer(t) push!(x, "") break end @@ -350,7 +350,7 @@ function pytryconvert(::Type{T}, x_) where {T} # get rules from the cache # TODO: we should hold weak references and clear the cache if types get deleted - tptr = C.Py_Type(getptr(x)) + tptr = C.Py_Type(x) trules = pyconvert_rules_cache(T) rules = get!(trules, tptr) do t = pynew(incref(tptr)) diff --git a/src/Convert/rules.jl b/src/Convert/rules.jl index a4a4a867..ac104d5e 100644 --- a/src/Convert/rules.jl +++ b/src/Convert/rules.jl @@ -61,8 +61,8 @@ pyconvert_rule_bytes(::Type{Base.CodeUnits{UInt8,String}}, x::Py) = pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin # first try to convert to Clonglong (or Culonglong if unsigned) v = - T <: Unsigned ? C.PyLong_AsUnsignedLongLong(getptr(x)) : - C.PyLong_AsLongLong(getptr(x)) + T <: Unsigned ? C.PyLong_AsUnsignedLongLong(x) : + C.PyLong_AsLongLong(x) if !iserrset_ambig(v) # success return pyconvert_tryconvert(T, v) diff --git a/src/Core/Py.jl b/src/Core/Py.jl index e56d74fa..355ace20 100644 --- a/src/Core/Py.jl +++ b/src/Core/Py.jl @@ -51,6 +51,11 @@ pyconvert(::Type{Py}, x::Py) = x setptr!(x::Py, ptr::C.PyPtr) = (setfield!(x, :ptr, ptr); x) +incref(x::Py) = (incref(getptr(x)); x) +decref(x::Py) = (decref(getptr(x)); x) + +Base.unsafe_convert(::Type{C.PyPtr}, x::Py) = getptr(x) + const PYNULL_CACHE = Py[] """ @@ -75,7 +80,7 @@ const PyNULL = pynew() pynew(ptr::C.PyPtr) = setptr!(pynew(), ptr) -pynew(x::Py) = pynew(incref(getptr(x))) +pynew(x::Py) = Base.GC.@preserve x pynew(incref(getptr(x))) """ pycopy!(dst::Py, src) @@ -164,13 +169,13 @@ Base.print(io::IO, x::Py) = print(io, string(x)) function Base.show(io::IO, x::Py) if get(io, :typeinfo, Any) == Py - if getptr(x) == C.PyNULL + if pyisnull(x) print(io, "NULL") else print(io, pyrepr(String, x)) end else - if getptr(x) == C.PyNULL + if pyisnull(x) print(io, "") else s = pyrepr(String, x) diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl index 6a501526..582e458e 100644 --- a/src/Core/builtins.jl +++ b/src/Core/builtins.jl @@ -15,7 +15,7 @@ pyisnot(x, y) = !pyis(x, y) Equivalent to `repr(x)` in Python. """ -pyrepr(x) = pynew(errcheck(@autopy x C.PyObject_Repr(getptr(x_)))) +pyrepr(x) = pynew(errcheck(@autopy x C.PyObject_Repr(x_))) pyrepr(::Type{String}, x) = (s = pyrepr(x); ans = pystr_asstring(s); pydel!(s); ans) export pyrepr @@ -24,7 +24,7 @@ export pyrepr Equivalent to `ascii(x)` in Python. """ -pyascii(x) = pynew(errcheck(@autopy x C.PyObject_ASCII(getptr(x_)))) +pyascii(x) = pynew(errcheck(@autopy x C.PyObject_ASCII(x_))) pyascii(::Type{String}, x) = (s = pyascii(x); ans = pystr_asstring(s); pydel!(s); ans) export pyascii @@ -36,7 +36,7 @@ Equivalent to `hasattr(x, k)` in Python. Tests if `getattr(x, k)` raises an `AttributeError`. """ function pyhasattr(x, k) - ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetAttr(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.AttributeError) errclear() @@ -49,7 +49,7 @@ function pyhasattr(x, k) return true end end -# pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(getptr(x_), getptr(k_))) == 1 +# pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(x_, k_)) == 1 export pyhasattr """ @@ -59,9 +59,9 @@ Equivalent to `getattr(x, k)` or `x.k` in Python. If `d` is specified, it is returned if the attribute does not exist. """ -pygetattr(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)))) +pygetattr(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetAttr(x_, k_))) function pygetattr(x, k, d) - ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetAttr(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.AttributeError) errclear() @@ -81,7 +81,7 @@ export pygetattr Equivalent to `setattr(x, k, v)` or `x.k = v` in Python. """ pysetattr(x, k, v) = ( - errcheck(@autopy x k v C.PyObject_SetAttr(getptr(x_), getptr(k_), getptr(v_))); nothing + errcheck(@autopy x k v C.PyObject_SetAttr(x_, k_, v_)); nothing ) export pysetattr @@ -91,7 +91,7 @@ export pysetattr Equivalent to `delattr(x, k)` or `del x.k` in Python. """ pydelattr(x, k) = - (errcheck(@autopy x k C.PyObject_SetAttr(getptr(x_), getptr(k_), C.PyNULL)); nothing) + (errcheck(@autopy x k C.PyObject_SetAttr(x_, k_, C.PyNULL)); nothing) export pydelattr """ @@ -100,7 +100,7 @@ export pydelattr Test if `s` is a subclass of `t`. Equivalent to `issubclass(s, t)` in Python. """ pyissubclass(s, t) = - errcheck(@autopy s t C.PyObject_IsSubclass(getptr(s_), getptr(t_))) == 1 + errcheck(@autopy s t C.PyObject_IsSubclass(s_, t_)) == 1 export pyissubclass """ @@ -109,7 +109,7 @@ export pyissubclass Test if `x` is of type `t`. Equivalent to `isinstance(x, t)` in Python. """ pyisinstance(x, t) = - errcheck(@autopy x t C.PyObject_IsInstance(getptr(x_), getptr(t_))) == 1 + errcheck(@autopy x t C.PyObject_IsInstance(x_, t_)) == 1 export pyisinstance """ @@ -117,7 +117,7 @@ export pyisinstance Equivalent to `hash(x)` in Python, converted to an `Integer`. """ -pyhash(x) = errcheck(@autopy x C.PyObject_Hash(getptr(x_))) +pyhash(x) = errcheck(@autopy x C.PyObject_Hash(x_)) export pyhash """ @@ -125,7 +125,7 @@ export pyhash The truthyness of `x`. Equivalent to `bool(x)` in Python, converted to a `Bool`. """ -pytruth(x) = errcheck(@autopy x C.PyObject_IsTrue(getptr(x_))) == 1 +pytruth(x) = errcheck(@autopy x C.PyObject_IsTrue(x_)) == 1 export pytruth """ @@ -133,7 +133,7 @@ export pytruth The falsyness of `x`. Equivalent to `not x` in Python, converted to a `Bool`. """ -pynot(x) = errcheck(@autopy x C.PyObject_Not(getptr(x_))) == 1 +pynot(x) = errcheck(@autopy x C.PyObject_Not(x_)) == 1 export pynot """ @@ -141,7 +141,7 @@ export pynot The length of `x`. Equivalent to `len(x)` in Python, converted to an `Integer`. """ -pylen(x) = errcheck(@autopy x C.PyObject_Length(getptr(x_))) +pylen(x) = errcheck(@autopy x C.PyObject_Length(x_)) export pylen """ @@ -150,7 +150,7 @@ export pylen Test if `pygetitem(x, k)` raises a `KeyError` or `AttributeError`. """ function pyhasitem(x, k) - ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetItem(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError) errclear() @@ -173,9 +173,9 @@ Equivalent `x[k]` in Python. If `d` is specified, it is returned if the item does not exist (i.e. if `x[k]` raises a `KeyError` or `IndexError`). """ -pygetitem(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)))) +pygetitem(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetItem(x_, k_))) function pygetitem(x, k, d) - ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetItem(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError) errclear() @@ -195,7 +195,7 @@ export pygetitem Equivalent to `setitem(x, k, v)` or `x[k] = v` in Python. """ pysetitem(x, k, v) = ( - errcheck(@autopy x k v C.PyObject_SetItem(getptr(x_), getptr(k_), getptr(v_))); nothing + errcheck(@autopy x k v C.PyObject_SetItem(x_, k_, v_)); nothing ) export pysetitem @@ -205,7 +205,7 @@ export pysetitem Equivalent to `delitem(x, k)` or `del x[k]` in Python. """ pydelitem(x, k) = - (errcheck(@autopy x k C.PyObject_DelItem(getptr(x_), getptr(k_))); nothing) + (errcheck(@autopy x k C.PyObject_DelItem(x_, k_)); nothing) export pydelitem """ @@ -213,15 +213,15 @@ export pydelitem Equivalent to `dir(x)` in Python. """ -pydir(x) = pynew(errcheck(@autopy x C.PyObject_Dir(getptr(x_)))) +pydir(x) = pynew(errcheck(@autopy x C.PyObject_Dir(x_))) export pydir -pycallargs(f) = pynew(errcheck(@autopy f C.PyObject_CallObject(getptr(f_), C.PyNULL))) +pycallargs(f) = pynew(errcheck(@autopy f C.PyObject_CallObject(f_, C.PyNULL))) pycallargs(f, args) = - pynew(errcheck(@autopy f args C.PyObject_CallObject(getptr(f_), getptr(args_)))) + pynew(errcheck(@autopy f args C.PyObject_CallObject(f_, args_))) pycallargs(f, args, kwargs) = pynew( errcheck( - @autopy f args kwargs C.PyObject_Call(getptr(f_), getptr(args_), getptr(kwargs_)) + @autopy f args kwargs C.PyObject_Call(f_, args_, kwargs_) ), ) @@ -255,7 +255,7 @@ export pycall Equivalent to `x == y` in Python. The second form converts to `Bool`. """ pyeq(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_EQ))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_EQ))) """ pyne(x, y) @@ -264,7 +264,7 @@ pyeq(x, y) = Equivalent to `x != y` in Python. The second form converts to `Bool`. """ pyne(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_NE))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_NE))) """ pyle(x, y) @@ -273,7 +273,7 @@ pyne(x, y) = Equivalent to `x <= y` in Python. The second form converts to `Bool`. """ pyle(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LE))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_LE))) """ pylt(x, y) @@ -282,7 +282,7 @@ pyle(x, y) = Equivalent to `x < y` in Python. The second form converts to `Bool`. """ pylt(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LT))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_LT))) """ pyge(x, y) @@ -291,7 +291,7 @@ pylt(x, y) = Equivalent to `x >= y` in Python. The second form converts to `Bool`. """ pyge(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_GE))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_GE))) """ pygt(x, y) @@ -300,19 +300,19 @@ pyge(x, y) = Equivalent to `x > y` in Python. The second form converts to `Bool`. """ pygt(x, y) = - pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_GT))) + pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_GT))) pyeq(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_EQ)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_EQ)) == 1 pyne(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_NE)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_NE)) == 1 pyle(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_LE)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_LE)) == 1 pylt(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_LT)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_LT)) == 1 pyge(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_GE)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_GE)) == 1 pygt(::Type{Bool}, x, y) = - errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_GT)) == 1 + errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_GT)) == 1 export pyeq, pyne, pyle, pylt, pyge, pygt """ @@ -320,7 +320,7 @@ export pyeq, pyne, pyle, pylt, pyge, pygt Equivalent to `v in x` in Python. """ -pycontains(x, v) = errcheck(@autopy x v C.PySequence_Contains(getptr(x_), getptr(v_))) == 1 +pycontains(x, v) = errcheck(@autopy x v C.PySequence_Contains(x_, v_)) == 1 export pycontains """ @@ -341,31 +341,31 @@ pynotin(v, x) = !pyin(v, x) Equivalent to `-x` in Python. """ -pyneg(x) = pynew(errcheck(@autopy x C.PyNumber_Negative(getptr(x_)))) +pyneg(x) = pynew(errcheck(@autopy x C.PyNumber_Negative(x_))) """ pypos(x) Equivalent to `+x` in Python. """ -pypos(x) = pynew(errcheck(@autopy x C.PyNumber_Positive(getptr(x_)))) +pypos(x) = pynew(errcheck(@autopy x C.PyNumber_Positive(x_))) """ pyabs(x) Equivalent to `abs(x)` in Python. """ -pyabs(x) = pynew(errcheck(@autopy x C.PyNumber_Absolute(getptr(x_)))) +pyabs(x) = pynew(errcheck(@autopy x C.PyNumber_Absolute(x_))) """ pyinv(x) Equivalent to `~x` in Python. """ -pyinv(x) = pynew(errcheck(@autopy x C.PyNumber_Invert(getptr(x_)))) +pyinv(x) = pynew(errcheck(@autopy x C.PyNumber_Invert(x_))) """ pyindex(x) Convert `x` losslessly to an `int`. """ -pyindex(x) = pynew(errcheck(@autopy x C.PyNumber_Index(getptr(x_)))) +pyindex(x) = pynew(errcheck(@autopy x C.PyNumber_Index(x_))) export pyneg, pypos, pyabs, pyinv, pyindex # binary @@ -374,81 +374,81 @@ export pyneg, pypos, pyabs, pyinv, pyindex Equivalent to `x + y` in Python. """ -pyadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Add(getptr(x_), getptr(y_)))) +pyadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Add(x_, y_))) """ pysub(x, y) Equivalent to `x - y` in Python. """ -pysub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Subtract(getptr(x_), getptr(y_)))) +pysub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Subtract(x_, y_))) """ pymul(x, y) Equivalent to `x * y` in Python. """ -pymul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Multiply(getptr(x_), getptr(y_)))) +pymul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Multiply(x_, y_))) """ pymatmul(x, y) Equivalent to `x @ y` in Python. """ pymatmul(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_MatrixMultiply(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_MatrixMultiply(x_, y_))) """ pyfloordiv(x, y) Equivalent to `x // y` in Python. """ pyfloordiv(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_FloorDivide(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_FloorDivide(x_, y_))) """ pytruediv(x, y) Equivalent to `x / y` in Python. """ -pytruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_TrueDivide(getptr(x_), getptr(y_)))) +pytruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_TrueDivide(x_, y_))) """ pymod(x, y) Equivalent to `x % y` in Python. """ -pymod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Remainder(getptr(x_), getptr(y_)))) +pymod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Remainder(x_, y_))) """ pydivmod(x, y) Equivalent to `divmod(x, y)` in Python. """ -pydivmod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Divmod(getptr(x_), getptr(y_)))) +pydivmod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Divmod(x_, y_))) """ pylshift(x, y) Equivalent to `x << y` in Python. """ -pylshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Lshift(getptr(x_), getptr(y_)))) +pylshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Lshift(x_, y_))) """ pyrshift(x, y) Equivalent to `x >> y` in Python. """ -pyrshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Rshift(getptr(x_), getptr(y_)))) +pyrshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Rshift(x_, y_))) """ pyand(x, y) Equivalent to `x & y` in Python. """ -pyand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_And(getptr(x_), getptr(y_)))) +pyand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_And(x_, y_))) """ pyxor(x, y) Equivalent to `x ^ y` in Python. """ -pyxor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Xor(getptr(x_), getptr(y_)))) +pyxor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Xor(x_, y_))) """ pyor(x, y) Equivalent to `x | y` in Python. """ -pyor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Or(getptr(x_), getptr(y_)))) +pyor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Or(x_, y_))) export pyadd, pysub, pymul, @@ -469,81 +469,81 @@ export pyadd, In-place add. `x = pyiadd(x, y)` is equivalent to `x += y` in Python. """ -pyiadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAdd(getptr(x_), getptr(y_)))) +pyiadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAdd(x_, y_))) """ pyisub(x, y) In-place subtract. `x = pyisub(x, y)` is equivalent to `x -= y` in Python. """ pyisub(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceSubtract(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceSubtract(x_, y_))) """ pyimul(x, y) In-place multiply. `x = pyimul(x, y)` is equivalent to `x *= y` in Python. """ pyimul(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceMultiply(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceMultiply(x_, y_))) """ pyimatmul(x, y) In-place matrix multiply. `x = pyimatmul(x, y)` is equivalent to `x @= y` in Python. """ pyimatmul(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceMatrixMultiply(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceMatrixMultiply(x_, y_))) """ pyifloordiv(x, y) In-place floor divide. `x = pyifloordiv(x, y)` is equivalent to `x //= y` in Python. """ pyifloordiv(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceFloorDivide(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceFloorDivide(x_, y_))) """ pyitruediv(x, y) In-place true division. `x = pyitruediv(x, y)` is equivalent to `x /= y` in Python. """ pyitruediv(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceTrueDivide(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceTrueDivide(x_, y_))) """ pyimod(x, y) In-place subtraction. `x = pyimod(x, y)` is equivalent to `x %= y` in Python. """ pyimod(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceRemainder(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceRemainder(x_, y_))) """ pyilshift(x, y) In-place left shift. `x = pyilshift(x, y)` is equivalent to `x <<= y` in Python. """ pyilshift(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceLshift(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceLshift(x_, y_))) """ pyirshift(x, y) In-place right shift. `x = pyirshift(x, y)` is equivalent to `x >>= y` in Python. """ pyirshift(x, y) = - pynew(errcheck(@autopy x y C.PyNumber_InPlaceRshift(getptr(x_), getptr(y_)))) + pynew(errcheck(@autopy x y C.PyNumber_InPlaceRshift(x_, y_))) """ pyiand(x, y) In-place and. `x = pyiand(x, y)` is equivalent to `x &= y` in Python. """ -pyiand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAnd(getptr(x_), getptr(y_)))) +pyiand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAnd(x_, y_))) """ pyixor(x, y) In-place xor. `x = pyixor(x, y)` is equivalent to `x ^= y` in Python. """ -pyixor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceXor(getptr(x_), getptr(y_)))) +pyixor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceXor(x_, y_))) """ pyior(x, y) In-place or. `x = pyior(x, y)` is equivalent to `x |= y` in Python. """ -pyior(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceOr(getptr(x_), getptr(y_)))) +pyior(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceOr(x_, y_))) export pyiadd, pyisub, pyimul, @@ -564,14 +564,14 @@ export pyiadd, Equivalent to `x ** y` or `pow(x, y, z)` in Python. """ pypow(x, y, z = pybuiltins.None) = - pynew(errcheck(@autopy x y z C.PyNumber_Power(getptr(x_), getptr(y_), getptr(z_)))) + pynew(errcheck(@autopy x y z C.PyNumber_Power(x_, y_, z_))) """ pyipow(x, y, z=None) In-place power. `x = pyipow(x, y)` is equivalent to `x **= y` in Python. """ pyipow(x, y, z = pybuiltins.None) = pynew( - errcheck(@autopy x y z C.PyNumber_InPlacePower(getptr(x_), getptr(y_), getptr(z_))), + errcheck(@autopy x y z C.PyNumber_InPlacePower(x_, y_, z_)), ) export pypow, pyipow @@ -582,7 +582,7 @@ export pypow, pyipow Equivalent to `iter(x)` in Python. """ -pyiter(x) = pynew(errcheck(@autopy x C.PyObject_GetIter(getptr(x_)))) +pyiter(x) = pynew(errcheck(@autopy x C.PyObject_GetIter(x_))) export pyiter """ @@ -598,7 +598,7 @@ export pynext Return the next item in the iterator `x`. When there are no more items, return NULL. """ -unsafe_pynext(x::Py) = Base.GC.@preserve x pynew(errcheck_ambig(C.PyIter_Next(getptr(x)))) +unsafe_pynext(x::Py) = Base.GC.@preserve x pynew(errcheck_ambig(C.PyIter_Next(x))) ### None @@ -640,15 +640,14 @@ pystr_fromUTF8(x) = pystr_fromUTF8(pointer(x), sizeof(x)) Convert `x` to a Python `str`. """ -pystr(x) = pynew(errcheck(@autopy x C.PyObject_Str(getptr(x_)))) +pystr(x) = pynew(errcheck(@autopy x C.PyObject_Str(x_))) pystr(x::String) = pystr_fromUTF8(x) pystr(x::SubString{String}) = pystr_fromUTF8(x) pystr(x::Char) = pystr(string(x)) pystr(::Type{String}, x) = (s = pystr(x); ans = pystr_asstring(s); pydel!(s); ans) export pystr -pystr_asUTF8bytes(x::Py) = - Base.GC.@preserve x pynew(errcheck(C.PyUnicode_AsUTF8String(getptr(x)))) +pystr_asUTF8bytes(x::Py) = pynew(errcheck(C.PyUnicode_AsUTF8String(x))) pystr_asUTF8vector(x::Py) = (b = pystr_asUTF8bytes(x); ans = pybytes_asvector(b); pydel!(b); ans) pystr_asstring(x::Py) = @@ -672,7 +671,7 @@ pybytes_fromdata(x) = pybytes_fromdata(pointer(x), sizeof(x)) Convert `x` to a Python `bytes`. """ -pybytes(x) = pynew(errcheck(@autopy x C.PyObject_Bytes(getptr(x_)))) +pybytes(x) = pynew(errcheck(@autopy x C.PyObject_Bytes(x_))) pybytes(x::Vector{UInt8}) = pybytes_fromdata(x) pybytes(x::Base.CodeUnits{UInt8,String}) = pybytes_fromdata(x) pybytes(x::Base.CodeUnits{UInt8,SubString{String}}) = pybytes_fromdata(x) @@ -687,7 +686,7 @@ pyisbytes(x) = pytypecheckfast(x, C.Py_TPFLAGS_BYTES_SUBCLASS) function pybytes_asdata(x::Py) ptr = Ref(Ptr{Cchar}(0)) len = Ref(C.Py_ssize_t(0)) - Base.GC.@preserve x errcheck(C.PyBytes_AsStringAndSize(getptr(x), ptr, len)) + errcheck(C.PyBytes_AsStringAndSize(x, ptr, len)) ptr[], len[] end @@ -729,7 +728,7 @@ function pyint(x::Unsigned) pyint_fallback(x) end end -pyint(x) = @autopy x pynew(errcheck(C.PyNumber_Long(getptr(x_)))) +pyint(x) = @autopy x pynew(errcheck(C.PyNumber_Long(x_))) export pyint pyisint(x) = pytypecheckfast(x, C.Py_TPFLAGS_LONG_SUBCLASS) @@ -742,12 +741,12 @@ pyisint(x) = pytypecheckfast(x, C.Py_TPFLAGS_LONG_SUBCLASS) Convert `x` to a Python `float`. """ pyfloat(x::Real = 0.0) = pynew(errcheck(C.PyFloat_FromDouble(x))) -pyfloat(x) = @autopy x pynew(errcheck(C.PyNumber_Float(getptr(x_)))) +pyfloat(x) = @autopy x pynew(errcheck(C.PyNumber_Float(x_))) export pyfloat pyisfloat(x) = pytypecheck(x, pybuiltins.float) -pyfloat_asdouble(x) = errcheck_ambig(@autopy x C.PyFloat_AsDouble(getptr(x_))) +pyfloat_asdouble(x) = errcheck_ambig(@autopy x C.PyFloat_AsDouble(x_)) ### complex @@ -766,7 +765,7 @@ export pycomplex pyiscomplex(x) = pytypecheck(x, pybuiltins.complex) function pycomplex_ascomplex(x) - c = @autopy x C.PyComplex_AsCComplex(getptr(x_)) + c = @autopy x C.PyComplex_AsCComplex(x_) c.real == -1 && c.imag == 0 && errcheck() return Complex(c.real, c.imag) end @@ -778,7 +777,7 @@ end The Python `type` of `x`. """ -pytype(x) = pynew(errcheck(@autopy x C.PyObject_Type(getptr(x_)))) +pytype(x) = pynew(errcheck(@autopy x C.PyObject_Type(x_))) export pytype """ @@ -856,8 +855,8 @@ end pyistype(x) = pytypecheckfast(x, C.Py_TPFLAGS_TYPE_SUBCLASS) -pytypecheck(x, t) = (@autopy x t C.Py_TypeCheck(getptr(x_), getptr(t_))) == 1 -pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(getptr(x_), f)) == 1 +pytypecheck(x, t) = (@autopy x t C.Py_TypeCheck(x_, t_)) == 1 +pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(x_, f)) == 1 ### slice @@ -867,7 +866,7 @@ pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(getptr(x_), f)) == 1 Construct a Python `slice`. Unspecified arguments default to `None`. """ pyslice(x, y, z = pybuiltins.None) = - pynew(errcheck(@autopy x y z C.PySlice_New(getptr(x_), getptr(y_), getptr(z_)))) + pynew(errcheck(@autopy x y z C.PySlice_New(x_, y_, z_))) pyslice(y) = pyslice(pybuiltins.None, y, pybuiltins.None) export pyslice @@ -894,12 +893,12 @@ pyisrange(x) = pytypecheck(x, pybuiltins.range) pynulltuple(len) = pynew(errcheck(C.PyTuple_New(len))) function pytuple_setitem(xs::Py, i, x) - errcheck(C.PyTuple_SetItem(getptr(xs), i, incref(getptr(Py(x))))) + errcheck(C.PyTuple_SetItem(xs, i, incref(Py(x)))) return xs end function pytuple_getitem(xs::Py, i) - Base.GC.@preserve xs pynew(incref(errcheck(C.PyTuple_GetItem(getptr(xs), i)))) + pynew(incref(errcheck(C.PyTuple_GetItem(xs, i)))) end function pytuple_fromiter(xs) @@ -950,13 +949,13 @@ pyistuple(x) = pytypecheckfast(x, C.Py_TPFLAGS_TUPLE_SUBCLASS) pynulllist(len) = pynew(errcheck(C.PyList_New(len))) function pylist_setitem(xs::Py, i, x) - errcheck(C.PyList_SetItem(getptr(xs), i, incref(getptr(Py(x))))) + errcheck(C.PyList_SetItem(xs, i, incref(Py(x)))) return xs end -pylist_append(xs::Py, x) = errcheck(@autopy x C.PyList_Append(getptr(xs), getptr(x_))) +pylist_append(xs::Py, x) = errcheck(@autopy x C.PyList_Append(xs, x_)) -pylist_astuple(x) = pynew(errcheck(@autopy x C.PyList_AsTuple(getptr(x_)))) +pylist_astuple(x) = pynew(errcheck(@autopy x C.PyList_AsTuple(x_))) function pylist_fromiter(xs) sz = Base.IteratorSize(typeof(xs)) @@ -1029,7 +1028,7 @@ export pyrowlist ### set -pyset_add(set::Py, x) = (errcheck(@autopy x C.PySet_Add(getptr(set), getptr(x_))); set) +pyset_add(set::Py, x) = (errcheck(@autopy x C.PySet_Add(set, x_)); set) function pyset_update_fromiter(set::Py, xs) for x in xs @@ -1067,7 +1066,7 @@ export pyfrozenset ### dict pydict_setitem(x::Py, k, v) = - errcheck(@autopy k v C.PyDict_SetItem(getptr(x), getptr(k_), getptr(v_))) + errcheck(@autopy k v C.PyDict_SetItem(x, k_, v_)) function pydict_fromiter(kvs) ans = pydict() @@ -1558,7 +1557,7 @@ Import a module `m`, or an attribute `k`, or a tuple of attributes. If several arguments are given, return the results of importing each one in a tuple. """ -pyimport(m) = pynew(errcheck(@autopy m C.PyImport_Import(getptr(m_)))) +pyimport(m) = pynew(errcheck(@autopy m C.PyImport_Import(m_))) pyimport((m, k)::Pair) = (m_ = pyimport(m); k_ = pygetattr(m_, k); pydel!(m_); k_) pyimport((m, ks)::Pair{<:Any,<:Tuple}) = (m_ = pyimport(m); ks_ = map(k -> pygetattr(m_, k), ks); pydel!(m_); ks_) diff --git a/src/Core/err.jl b/src/Core/err.jl index 1895bcb7..5cde77ec 100644 --- a/src/Core/err.jl +++ b/src/Core/err.jl @@ -15,7 +15,7 @@ errcheck_ambig(val) = iserrset_ambig(val) ? pythrow() : val errclear() = C.PyErr_Clear() -errmatches(t) = (@autopy t C.PyErr_ExceptionMatches(getptr(t_))) == 1 +errmatches(t) = (@autopy t C.PyErr_ExceptionMatches(t_)) == 1 function errget() t = Ref(C.PyNULL) @@ -25,17 +25,14 @@ function errget() (pynew(t[]), pynew(v[]), pynew(b[])) end -errset(t::Py) = Base.GC.@preserve t C.PyErr_SetNone(getptr(t)) -errset(t::Py, v::Py) = Base.GC.@preserve t v C.PyErr_SetObject(getptr(t), getptr(v)) -errset(t::Py, v::String) = Base.GC.@preserve t C.PyErr_SetString(getptr(t), v) +errset(t::Py) = C.PyErr_SetNone(t) +errset(t::Py, v::Py) = C.PyErr_SetObject(t, v) +errset(t::Py, v::String) = C.PyErr_SetString(t, v) function errnormalize!(t::Py, v::Py, b::Py) - tptr = getptr(t) - vptr = getptr(v) - bptr = getptr(b) - tref = Ref(tptr) - vref = Ref(vptr) - bref = Ref(bptr) + tref = Ref(getptr(t)) + vref = Ref(getptr(v)) + bref = Ref(getptr(b)) C.PyErr_NormalizeException(tref, vref, bref) setptr!(t, tref[]) setptr!(v, vref[]) @@ -80,9 +77,9 @@ end function Base.getproperty(exc::PyException, k::Symbol) if k in (:t, :v, :b) && !exc._isnormalized errnormalize!(exc._t, exc._v, exc._b) - pyisnull(exc._t) && setptr!(exc._t, incref(getptr(pybuiltins.None))) - pyisnull(exc._v) && setptr!(exc._v, incref(getptr(pybuiltins.None))) - pyisnull(exc._b) && setptr!(exc._b, incref(getptr(pybuiltins.None))) + pyisnull(exc._t) && pycopy!(exc._t, pybuiltins.None) + pyisnull(exc._v) && pycopy!(exc._v, pybuiltins.None) + pyisnull(exc._b) && pycopy!(exc._b, pybuiltins.None) pyisnone(exc._v) || (exc._v.__traceback__ = exc._b) exc._isnormalized = true end diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index 7b181881..fa96dd36 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -337,11 +337,12 @@ function __init__() init_c() end -PyJuliaValue_IsNull(o::C.PyPtr) = UnsafePtr{PyJuliaValueObject}(o).value[] == 0 +PyJuliaValue_IsNull(o) = Base.GC.@preserve o UnsafePtr{PyJuliaValueObject}(C.asptr(o)).value[] == 0 -PyJuliaValue_GetValue(o::C.PyPtr) = PYJLVALUES[UnsafePtr{PyJuliaValueObject}(o).value[]] +PyJuliaValue_GetValue(o) = Base.GC.@preserve o PYJLVALUES[UnsafePtr{PyJuliaValueObject}(C.asptr(o)).value[]] -PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin +PyJuliaValue_SetValue(_o, @nospecialize(v)) = Base.GC.@preserve _o begin + o = C.asptr(_o) idx = UnsafePtr{PyJuliaValueObject}(o).value[] if idx == 0 if isempty(PYJLFREEVALUES) @@ -358,7 +359,8 @@ PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin nothing end -PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) = begin +PyJuliaValue_New(_t, @nospecialize(v)) = Base.GC.@preserve _t begin + t = C.asptr(_t) if C.PyType_IsSubtype(t, PyJuliaBase_Type[]) != 1 C.PyErr_SetString( C.POINTERS.PyExc_TypeError, diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 008d3342..47ffb084 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -1,10 +1,10 @@ const pyjlbasetype = pynew() -_pyjl_getvalue(x) = @autopy x Cjl.PyJuliaValue_GetValue(getptr(x_)) +_pyjl_getvalue(x) = @autopy x Cjl.PyJuliaValue_GetValue(x_) -_pyjl_setvalue!(x, v) = @autopy x Cjl.PyJuliaValue_SetValue(getptr(x_), v) +_pyjl_setvalue!(x, v) = @autopy x Cjl.PyJuliaValue_SetValue(x_, v) -pyjl(t, v) = pynew(errcheck(@autopy t Cjl.PyJuliaValue_New(getptr(t_), v))) +pyjl(t, v) = pynew(errcheck(@autopy t Cjl.PyJuliaValue_New(t_, v))) """ pyisjl(x) @@ -16,7 +16,7 @@ export pyisjl pyjlisnull(x) = @autopy x begin if pyisjl(x_) - Cjl.PyJuliaValue_IsNull(getptr(x_)) + Cjl.PyJuliaValue_IsNull(x_) else error("Expecting a 'juliacall.ValueBase', got a '$(pytype(x_).__name__)'") end @@ -85,13 +85,13 @@ function Cjl._pyjl_callmethod(f, self_::C.PyPtr, args_::C.PyPtr, nargs::C.Py_ssi "__jl_callmethod not implemented for this many arguments", ) end - return incref(getptr(ans)) + return getptr(incref(ans)) catch exc if exc isa PyException Base.GC.@preserve exc C.PyErr_Restore( - incref(getptr(exc._t)), - incref(getptr(exc._v)), - incref(getptr(exc._b)), + incref(exc._t), + incref(exc._v), + incref(exc._b), ) return C.PyNULL else @@ -126,7 +126,7 @@ function pyjl_handle_error(f, self, exc) return C.PyNULL else # Otherwise, return the given object (e.g. NotImplemented) - return Base.GC.@preserve t incref(getptr(t)) + return getptr(incref(t)) end end diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 49d9547f..c7ee0c15 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -102,7 +102,7 @@ function pyjlbinaryio_readinto(io::IO, b::Py) return PyNULL end pydel!(c) - buf = unsafe_load(C.PyMemoryView_GET_BUFFER(getptr(m))) + buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m_)) if buf.readonly != 0 pydel!(m) errset(pybuiltins.ValueError, "output buffer is read-only") @@ -125,7 +125,7 @@ function pyjlbinaryio_write(io::IO, b::Py) return PyNULL end pydel!(c) - buf = unsafe_load(C.PyMemoryView_GET_BUFFER(getptr(m))) + buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m)) data = unsafe_wrap(Array, Ptr{UInt8}(buf.buf), buf.len) write(io, data) pydel!(m) diff --git a/src/JlWrap/objectarray.jl b/src/JlWrap/objectarray.jl index a98189e8..f7db94ac 100644 --- a/src/JlWrap/objectarray.jl +++ b/src/JlWrap/objectarray.jl @@ -51,7 +51,7 @@ end @boundscheck checkbounds(x, i...) v_ = Py(v) @inbounds decref(x.ptrs[i...]) - @inbounds x.ptrs[i...] = incref(getptr(v_)) + @inbounds x.ptrs[i...] = getptr(incref(v_)) return x end diff --git a/src/Wrap/PyArray.jl b/src/Wrap/PyArray.jl index 37dea3ac..28351eae 100644 --- a/src/Wrap/PyArray.jl +++ b/src/Wrap/PyArray.jl @@ -123,7 +123,7 @@ function pyarray_make( @debug "failed to make PyArray from __array_interface__" exc = exc end end - if buffer && C.PyObject_CheckBuffer(getptr(x)) + if buffer && C.PyObject_CheckBuffer(x) try return pyarray_make(A, x, PyArraySource_Buffer(x)) catch exc @@ -246,7 +246,7 @@ function PyArraySource_ArrayInterface(x::Py, d::Py = x.__array_interface__) else memview = @py memoryview(data === None ? x : data) pydel!(data) - buf = UnsafePtr(C.PyMemoryView_GET_BUFFER(getptr(memview))) + buf = UnsafePtr(C.PyMemoryView_GET_BUFFER(memview)) ptr = buf.buf[!] readonly = buf.readonly[] != 0 handle = Py((x, memview)) @@ -411,8 +411,8 @@ struct PyArraySource_ArrayStruct <: PyArraySource info::C.PyArrayInterface end function PyArraySource_ArrayStruct(x::Py, capsule::Py = x.__array_struct__) - name = C.PyCapsule_GetName(getptr(capsule)) - ptr = C.PyCapsule_GetPointer(getptr(capsule), name) + name = C.PyCapsule_GetName(capsule) + ptr = C.PyCapsule_GetPointer(capsule, name) info = unsafe_load(Ptr{C.PyArrayInterface}(ptr)) @assert info.two == 2 return PyArraySource_ArrayStruct(x, capsule, info) @@ -540,7 +540,7 @@ struct PyArraySource_Buffer <: PyArraySource end function PyArraySource_Buffer(x::Py) memview = pybuiltins.memoryview(x) - buf = C.UnsafePtr(C.PyMemoryView_GET_BUFFER(getptr(memview))) + buf = C.UnsafePtr(C.PyMemoryView_GET_BUFFER(memview)) PyArraySource_Buffer(x, memview, buf) end @@ -703,7 +703,7 @@ function pyarray_store!(p::Ptr{R}, x::T) where {R,T} elseif R == UnsafePyObject @autopy x begin decref(unsafe_load(p).ptr) - unsafe_store!(p, UnsafePyObject(incref(getptr(x_)))) + unsafe_store!(p, UnsafePyObject(getptr(incref(x_)))) end else unsafe_store!(p, convert(R, x)) From 3d482743f9b9da9c9de0f42f4b5ec135a009fcd6 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Wed, 4 Jun 2025 17:07:24 +0100 Subject: [PATCH 15/20] ci: update to new ASV --- .github/workflows/benchmark_pr.yml | 66 ++++-------------------------- 1 file changed, 9 insertions(+), 57 deletions(-) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index 4ad1b6a1..6bf0f498 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -1,64 +1,16 @@ -name: Benchmark a pull request +name: Benchmark PR on: pull_request_target: - branches: - - main + branches: [ main ] permissions: - pull-requests: write + pull-requests: write # needed to post comments jobs: - generate_plots: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 - with: - version: "1.9" - - uses: julia-actions/cache@v1 - - name: Extract Package Name from Project.toml - id: extract-package-name - run: | - PACKAGE_NAME=$(grep "^name" Project.toml | sed 's/^name = "\(.*\)"$/\1/') - echo "::set-output name=package_name::$PACKAGE_NAME" - - name: Build AirspeedVelocity - env: - JULIA_NUM_THREADS: 2 - run: | - # Lightweight build step, as sometimes the runner runs out of memory: - julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.add(;url="https://github.com/MilesCranmer/AirspeedVelocity.jl.git")' - julia -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0; import Pkg; Pkg.build("AirspeedVelocity")' - - name: Add ~/.julia/bin to PATH - run: | - echo "$HOME/.julia/bin" >> $GITHUB_PATH - - name: Run benchmarks - run: | - echo $PATH - ls -l ~/.julia/bin - mkdir results - benchpkg ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --url=${{ github.event.repository.clone_url }} --bench-on="${{github.event.pull_request.head.sha}}" --output-dir=results/ --tune --exeflags="-O3 --threads=auto" - - name: Create markdown table from benchmarks - run: | - benchpkgtable ${{ steps.extract-package-name.outputs.package_name }} --rev="${{github.event.repository.default_branch}},${{github.event.pull_request.head.sha}}" --input-dir=results/ --ratio > table.md - echo '### Benchmark Results' > body.md - echo '' >> body.md - echo '' >> body.md - cat table.md >> body.md - echo '' >> body.md - - name: Find Comment - uses: peter-evans/find-comment@v2 - id: fcbenchmark - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Benchmark Results - - - name: Comment on PR - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.fcbenchmark.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body-path: body.md - edit-mode: replace + bench: + runs-on: ubuntu-latest + steps: + - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 + with: + julia-version: '1' From 31d9b85453e4cbec0437b3d4f50c73f808f9d631 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Wed, 4 Jun 2025 17:10:58 +0100 Subject: [PATCH 16/20] ci: switch to job summary version --- .github/workflows/benchmark_pr.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index 6bf0f498..71ccf7a6 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -1,16 +1,13 @@ name: Benchmark PR - on: - pull_request_target: + pull_request: branches: [ main ] -permissions: - pull-requests: write # needed to post comments - jobs: bench: runs-on: ubuntu-latest steps: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: - julia-version: '1' + julia-version: "1" + job-summary: "true" # Post to "summary" tab of workflow run From 9206cb2fa6fbb27fc3eb1952bb2394ee9be709d0 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Wed, 4 Jun 2025 17:18:40 +0100 Subject: [PATCH 17/20] ci: bench on PR sha --- .github/workflows/benchmark_pr.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index 71ccf7a6..c266302e 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -10,4 +10,7 @@ jobs: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: julia-version: "1" - job-summary: "true" # Post to "summary" tab of workflow run + # Post to "summary" tab of workflow run: + job-summary: "true" + # Run benchmark using PR's version of the script: + bench-on: ${{ github.event.pull_request.head.sha }} From abd1e69c2ff259f50c4479662a3c218877cf7f9b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Wed, 4 Jun 2025 17:53:17 +0100 Subject: [PATCH 18/20] ci: run on PR to any branch, not just main --- .github/workflows/benchmark_pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index c266302e..bfa35af4 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -1,7 +1,6 @@ name: Benchmark PR on: pull_request: - branches: [ main ] jobs: bench: From 4b8b25aada9be6e91867b5d1db5cf088e5e4e6e2 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Wed, 4 Jun 2025 18:04:15 +0100 Subject: [PATCH 19/20] ci: run tuned benchmarks --- .github/workflows/benchmark_pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/benchmark_pr.yml b/.github/workflows/benchmark_pr.yml index bfa35af4..c8722bcf 100644 --- a/.github/workflows/benchmark_pr.yml +++ b/.github/workflows/benchmark_pr.yml @@ -9,6 +9,7 @@ jobs: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: julia-version: "1" + tune: "true" # Post to "summary" tab of workflow run: job-summary: "true" # Run benchmark using PR's version of the script: From 66d3fe25e47e92ee3be019cb1b224e310ac0bc72 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 6 Jul 2025 18:55:39 +0100 Subject: [PATCH 20/20] undo changes from merge --- src/Convert/ctypes.jl | 14 ++++++++------ src/Core/Py.jl | 4 ++-- src/Core/builtins.jl | 2 +- src/JlWrap/io.jl | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Convert/ctypes.jl b/src/Convert/ctypes.jl index 2b5fe449..d18bbc74 100644 --- a/src/Convert/ctypes.jl +++ b/src/Convert/ctypes.jl @@ -1,12 +1,14 @@ struct pyconvert_rule_ctypessimplevalue{R,S} <: Function end function (::pyconvert_rule_ctypessimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T} - ptr = C.PySimpleObject_GetValue(Ptr{R}, x) - ans = unsafe_load(ptr) - if SAFE - pyconvert_return(convert(T, ans)) - else - pyconvert_tryconvert(T, ans) + Base.GC.@preserve x begin + ptr = C.PySimpleObject_GetValue(Ptr{R}, x) + ans = unsafe_load(ptr) + if SAFE + pyconvert_return(convert(T, ans)) + else + pyconvert_tryconvert(T, ans) + end end end diff --git a/src/Core/Py.jl b/src/Core/Py.jl index 355ace20..a457ce90 100644 --- a/src/Core/Py.jl +++ b/src/Core/Py.jl @@ -51,8 +51,8 @@ pyconvert(::Type{Py}, x::Py) = x setptr!(x::Py, ptr::C.PyPtr) = (setfield!(x, :ptr, ptr); x) -incref(x::Py) = (incref(getptr(x)); x) -decref(x::Py) = (decref(getptr(x)); x) +incref(x::Py) = Base.GC.@preserve x (incref(getptr(x)); x) +decref(x::Py) = Base.GC.@preserve x (decref(getptr(x)); x) Base.unsafe_convert(::Type{C.PyPtr}, x::Py) = getptr(x) diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl index 582e458e..ec7eb10b 100644 --- a/src/Core/builtins.jl +++ b/src/Core/builtins.jl @@ -898,7 +898,7 @@ function pytuple_setitem(xs::Py, i, x) end function pytuple_getitem(xs::Py, i) - pynew(incref(errcheck(C.PyTuple_GetItem(xs, i)))) + Base.GC.@preserve xs pynew(incref(errcheck(C.PyTuple_GetItem(xs, i)))) end function pytuple_fromiter(xs) diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index c7ee0c15..ac5d9085 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -102,7 +102,7 @@ function pyjlbinaryio_readinto(io::IO, b::Py) return PyNULL end pydel!(c) - buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m_)) + buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m)) if buf.readonly != 0 pydel!(m) errset(pybuiltins.ValueError, "output buffer is read-only")