Skip to content

Commit

Permalink
A few performance improvements (#263)
Browse files Browse the repository at this point in the history
* workaround slow dynamic dispatch when creating objects

* cache the utf8 array instead of recreating it for every number

* microptimize the check for valid digit

* update ci files

* outline throwing char

constructing the error message generates quite a lot of code which makes inlining get an inaccurate view.

outlining is a simple fix and reduces the number of active basic blocks in the code from 26 to 9
  • Loading branch information
KristofferC authored and TotalVerb committed Nov 4, 2018
1 parent 192007d commit e8d39a6
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 19 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ os:
- linux
julia:
- 0.7
- 1.0
- nightly
notifications:
email: false
sudo: false
script:
- julia -e 'import Pkg; Pkg.clone(pwd()); Pkg.build("JSON"); Pkg.test("JSON"; coverage=true)';
after_success:
- julia -e 'cd(Pkg.dir("JSON")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())';
- julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())';
19 changes: 13 additions & 6 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
environment:
matrix:
- julia_version: 0.7
- julia_version: latest
- julia_version: 1
- julia_version: nightly

platform:
- x86 # 32-bit
- x64 # 64-bit

## uncomment the following lines to allow failures on nightly julia
## (tests will run but not make your overall status red)
#matrix:
# allow_failures:
# - julia_version: latest
# # Uncomment the following lines to allow failures on nightly julia
# # (tests will run but not make your overall status red)
# matrix:
# allow_failures:
# - julia_version: nightly

branches:
only:
Expand All @@ -34,3 +35,9 @@ build_script:
test_script:
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"

# # Uncomment to support code coverage upload. Should only be enabled for packages
# # which would have coverage gaps without running on Windows
# on_success:
# - echo "%JL_CODECOV_SCRIPT%"
# - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"
34 changes: 25 additions & 9 deletions src/Parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ abstract type ParserState end
mutable struct MemoryParserState <: ParserState
utf8::String
s::Int
utf8array::Vector{UInt8}
end

# it is convenient to access MemoryParserState like a Vector{UInt8} to avoid copies
Expand All @@ -31,8 +32,9 @@ mutable struct StreamingParserState{T <: IO} <: ParserState
io::T
cur::UInt8
used::Bool
utf8array::Vector{UInt8}
end
StreamingParserState(io::IO) = StreamingParserState(io, 0x00, true)
StreamingParserState(io::IO) = StreamingParserState(io, 0x00, true, UInt8[])

struct ParserContext{DictType, IntType} end

Expand Down Expand Up @@ -76,9 +78,10 @@ skip past that byte. Otherwise, an error is thrown.
if byteat(ps) == c
incr!(ps)
else
_error("Expected '$(Char(c))' here", ps)
_error_expected_char(c, ps)
end
end
@noinline _error_expected_char(c, ps) = _error("Expected '$(Char(c))' here", ps)

function skip!(ps::ParserState, cs::UInt8...)
for c in cs
Expand Down Expand Up @@ -328,8 +331,9 @@ function int_from_bytes(pc::ParserContext{<:Any,IntType},
num = IntType(0)
@inbounds for i in from:to
c = bytes[i]
if isjsondigit(c)
num = IntType(10) * num + IntType(c - DIGIT_ZERO)
dig = c - DIGIT_ZERO
if dig < 0x10
num = IntType(10) * num + IntType(dig)
else
_error(E_BAD_NUMBER, ps)
end
Expand Down Expand Up @@ -362,7 +366,7 @@ end
function parse_number(pc::ParserContext, ps::ParserState)
# Determine the end of the floating point by skipping past ASCII values
# 0-9, +, -, e, E, and .
number = UInt8[]
number = ps.utf8array
isint = true

@inbounds while hasmore(ps)
Expand All @@ -380,7 +384,9 @@ function parse_number(pc::ParserContext, ps::ParserState)
incr!(ps)
end

number_from_bytes(pc, ps, isint, number, 1, length(number))
v = number_from_bytes(pc, ps, isint, number, 1, length(number))
resize!(number, 0)
return v
end

unparameterize_type(x) = x # Fallback for nontypes -- functions etc
Expand All @@ -389,11 +395,21 @@ function unparameterize_type(T::Type)
candidate <: Union{} ? T : candidate
end

# Workaround for slow dynamic dispatch for creating objects
const DEFAULT_PARSERCONTEXT = ParserContext{Dict{String, Any}, Int64}()
function _get_parsercontext(dicttype, inttype)
if dicttype == Dict{String, Any} && inttype == Int64
DEFAULT_PARSERCONTEXT
else
ParserContext{unparameterize_type(dicttype), inttype}.instance
end
end

function parse(str::AbstractString;
dicttype=Dict{String,Any},
inttype::Type{<:Real}=Int64)
pc = ParserContext{unparameterize_type(dicttype), inttype}()
ps = MemoryParserState(str, 1)
pc = _get_parsercontext(dicttype, inttype)
ps = MemoryParserState(str, 1, UInt8[])
v = parse_value(pc, ps)
chomp_space!(ps)
if hasmore(ps)
Expand All @@ -405,7 +421,7 @@ end
function parse(io::IO;
dicttype=Dict{String,Any},
inttype::Type{<:Real}=Int64)
pc = ParserContext{unparameterize_type(dicttype), inttype}()
pc = _get_parsercontext(dicttype, inttype)
ps = StreamingParserState(io)
parse_value(pc, ps)
end
Expand Down

0 comments on commit e8d39a6

Please sign in to comment.