Skip to content

Commit dca8d75

Browse files
willtebbuttgithub-actions[bot]iamed2
authored
getindex(::Column) performance (#252)
* Initial refactor * Remove allocations * Remove commented code * Revert code movement * Bump patch * Remove indirection * Revert wrapper * Sort out formatting * Remove formatting issues * Initial performance regression tests * Uncommnt tests * Make allocation tests more robust * Naming and count_allocs and binary_format * Add string test * Uncomment code * Remove test code * Revert test data extraction * Formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Merge reversion * Update test/runtests.jl Co-authored-by: Eric Davies <iamed2@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Eric Davies <iamed2@gmail.com>
1 parent 5ba8155 commit dca8d75

File tree

4 files changed

+66
-15
lines changed

4 files changed

+66
-15
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LibPQ"
22
uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
33
license = "MIT"
4-
version = "1.13.0"
4+
version = "1.14.0"
55

66
[deps]
77
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"

src/parsing.jl

+5-4
Original file line numberDiff line numberDiff line change
@@ -683,12 +683,13 @@ end
683683

684684
struct FallbackConversion <: AbstractDict{Tuple{Oid,Type},Base.Callable} end
685685

686+
struct ParseType{T} <: Function end
687+
688+
(::ParseType{typ})(pqv::PQValue) where {typ} = parse(typ, pqv)
689+
686690
function Base.getindex(cmap::FallbackConversion, oid_typ::Tuple{Integer,Type})
687691
_, typ = oid_typ
688-
689-
return function parse_type(pqv::PQValue)
690-
return parse(typ, pqv)
691-
end
692+
return ParseType{typ}()
692693
end
693694

694695
Base.haskey(cmap::FallbackConversion, oid_typ::Tuple{Integer,Type}) = true

src/tables.jl

+12-10
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,12 @@ end
5151

5252
# Columns
5353

54-
struct Column{T} <: AbstractVector{T}
55-
result::Result
54+
struct Column{T,oid,typ,Tresult<:Result,Tfunc<:Base.Callable} <: AbstractVector{T}
55+
result::Tresult
5656
col::Int
5757
col_name::Symbol
58-
oid::Oid
5958
not_null::Bool
60-
typ::Type
61-
func::Base.Callable
59+
func::Tfunc
6260
end
6361

6462
struct Columns <: AbstractVector{Column}
@@ -86,7 +84,9 @@ function Tables.schema(cs::Columns)
8684
return Tables.Schema(map(Symbol, column_names(jl_result)), types)
8785
end
8886

89-
function Column(jl_result::Result, col::Integer, name=Symbol(column_name(jl_result, col)))
87+
function Column(
88+
jl_result::Tresult, col::Integer, name=Symbol(column_name(jl_result, col))
89+
) where {Tresult<:Result}
9090
@boundscheck if !checkindex(Bool, Base.OneTo(num_columns(jl_result)), col)
9191
throw(BoundsError(Columns(jl_result), col))
9292
end
@@ -95,8 +95,10 @@ function Column(jl_result::Result, col::Integer, name=Symbol(column_name(jl_resu
9595
typ = column_types(jl_result)[col]
9696
func = jl_result.column_funcs[col]
9797
not_null = jl_result.not_null[col]
98-
element_type = not_null ? typ : Union{typ, Missing}
99-
return Column{element_type}(jl_result, col, name, oid, not_null, typ, func)
98+
element_type = not_null ? typ : Union{typ,Missing}
99+
return Column{element_type,oid,typ,Tresult,typeof(func)}(
100+
jl_result, col, name, not_null, func
101+
)
100102
end
101103

102104
function Column(jl_result::Result, name::Symbol, col=column_number(jl_result, name))
@@ -107,13 +109,13 @@ result(c::Column) = getfield(c, :result)
107109
column_number(c::Column) = getfield(c, :col)
108110
column_name(c::Column) = getfield(c, :col_name)
109111

110-
function Base.getindex(c::Column{T}, row::Integer)::T where T
112+
function Base.getindex(c::Column{T,oid,typ}, row::Integer)::T where {T,oid,typ}
111113
jl_result = result(c)
112114
col = column_number(c)
113115
if isnull(jl_result, row, col)
114116
return missing
115117
else
116-
return c.func(PQValue{c.oid}(jl_result, row, col))::c.typ
118+
return c.func(PQValue{oid}(jl_result, row, col))::typ
117119
end
118120
end
119121

test/runtests.jl

+48
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ macro test_nolog_on_windows(ex...)
3232
end
3333
end
3434

35+
# Copied from `@time`.
36+
function count_allocs(f, args...)
37+
stats = @timed f(args...)
38+
return Base.gc_alloc_count(stats.gcstats)
39+
end
40+
3541
@testset "LibPQ" begin
3642

3743
@testset "ConninfoDisplay" begin
@@ -1612,6 +1618,48 @@ end
16121618
close(conn)
16131619
@test !isopen(conn)
16141620
end
1621+
1622+
@testset "getindex(::Column) performance" begin
1623+
@testset "$in_val, bin=$bin_fmt" for (
1624+
in_val, out_val, bin_fmt, num_allocs
1625+
) in [
1626+
("5::float8", 5.0, true, 0),
1627+
("5::float4", 5.0f0, true, 0),
1628+
("3::int8", Int64(3), true, 0),
1629+
("3::int4", Int32(3), true, 0),
1630+
("3::int2", Int16(3), true, 0),
1631+
("'hello'::varchar", "hello", true, 1),
1632+
("5::float8", 5.0, false, 2),
1633+
("5::float4", 5.0f0, false, 2),
1634+
("3::int8", Int64(3), false, 2),
1635+
("3::int4", Int32(3), false, 2),
1636+
("3::int2", Int16(3), false, 2),
1637+
("'hello'::varchar", "hello", false, 3),
1638+
]
1639+
1640+
# Establish connection and construct temporary table.
1641+
conn = LibPQ.Connection("dbname=postgres user=$DATABASE_USER")
1642+
1643+
# Get the column.
1644+
result = execute(
1645+
conn, "SELECT $in_val AS my_column;"; binary_format=bin_fmt,
1646+
)
1647+
col = columntable(result).my_column
1648+
1649+
# Ensure that element is of expected type and value.
1650+
@test typeof(col[1]) == typeof(out_val)
1651+
@test col[1] == out_val
1652+
1653+
# Ensure that getting an element from the column produces num_allocs allocs.
1654+
foo(col) = [col[1] for _ in 1:100]
1655+
count_allocs(foo, col)
1656+
max_expected_allocs = num_allocs * 100 + 5
1657+
@test count_allocs(foo, col) < max_expected_allocs
1658+
1659+
close(result)
1660+
close(conn)
1661+
end
1662+
end
16151663
end
16161664

16171665
@testset "Statements" begin

0 commit comments

Comments
 (0)