# Understanding performance using \`@btime\` and \`@code\_warntype\`, \`@code\_llvm\`, etc

**URL:** <https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293>\
**Category:** Performance\
**Created:** [October 13, 2018, 9:51pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293 "2018-10-13T21:51:12Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![dave.f.kleinschmidt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dave.f.kleinschmidt/32/55_2.png) [@dave.f.kleinschmidt](https://discourse.julialang.org/u/dave.f.kleinschmidt)\
**Post date:** [October 13, 2018, 9:51pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/1 "2018-10-13T21:51:13Z")

</div>

I’m struggling to get my head around some benchmarking and `@code_llvm` inspecting that I’ve been doing. The background is I was curious what kind of overhead you get from creating an intermediate `Normal` distribution in a custom function to calculate the PDF (as in R `dnorm`), like so:

```julia
julia> using Distributions

julia> dnorm(x, μ=0., σ=1.) = pdf(Normal(μ, σ), x)
dnorm (generic function with 3 methods)

```

My intuition was that, since `Normal` is immutable and just holds two floats (the mean and stddev) the compiler should be able to that out and compile away the wrapper all together. A quick look at the `@code_warntype` seems to confirm this:

```julia
julia> @code_warntype dnorm(1., 0., 1.)
Body::Float64
1 1 ─ %1 = (Base.lt_float)(0.0, σ)::Bool │╻╷╷╷╷ Type
  │ %2 = (Base.not_int)(%1)::Bool ││╻ Type
  └── goto #3 if not %2 │││┃ macro expansion
  2 ─ %4 = invoke Distributions.string("Normal"::String, ": the condition "::String, "σ > zero(σ)"::Vararg{String,N} where N, " is not satisfied.")::String ││││
  │ %5 = %new(Core.ArgumentError, %4)::ArgumentError ││││╻ Type
  │ (Distributions.throw)(%5) ││││
  └── $(Expr(:unreachable)) ││││
  3 ─ goto #4 ││││
  4 ─ goto #5 ││
  5 ─ %10 = (Base.sub_float)(x, μ)::Float64 ││╻╷╷ normpdf
  │ %11 = (Base.div_float)(%10, σ)::Float64 │││╻ zval
  │ %12 = (Base.mul_float)(%11, %11)::Float64 ││││╻╷ abs2
  │ %13 = (Base.neg_float)(%12)::Float64 ││││╻ -
  │ %14 = (Base.div_float)(%13, 2.0)::Float64 │││││╻ /
  │ %15 = invoke StatsFuns.exp(%14::Float64)::Float64 ││││
  │ %16 = (Base.mul_float)(%15, 0.3989422804014327)::Float64 │││││╻ *
  │ %17 = (Base.div_float)(%16, σ)::Float64 │││╻ /
  └── return %17 │

```

But. The performance is about 4x slower than when I implement the underlying calculation directly (or use the equivalent StatsFuns `normpdf` function):

```julia
julia> dnorm_manual(x, μ, σ) = exp(abs2((x-μ)/σ) * -0.5) / (σ*sqrt(2π))
dnorm_manual (generic function with 1 method)

julia> @btime dnorm(1., 0., 1.)
  6.704 ns (0 allocations: 0 bytes)
0.24197072451914337

julia> @btime dnorm_manual(1., 0., 1.)
  1.637 ns (0 allocations: 0 bytes)
0.24197072451914337

julia> using StatsFuns; @btime normpdf(0., 1., 1.)
  1.637 ns (0 allocations: 0 bytes)
0.24197072451914337

```

As far as I can tell, the only real difference here is the checks from the `Normal` constructor (that the stddev is positive) (along with the fact that manual version isn’t not using the StatsFuns special constants 1/\sqrt{2\pi}):

```julia
julia> @code_warntype dnorm_manual(1., 0., 1.)
Body::Float64
1 1 ─ %1 = (Base.sub_float)(x, μ)::Float64 │╻ -
  │ %2 = (Base.div_float)(%1, σ)::Float64 │╻ /
  │ %3 = (Base.mul_float)(%2, %2)::Float64 ││╻ *
  │ %4 = (Base.mul_float)(%3, -0.5)::Float64 │╻ *
  │ %5 = invoke Main.exp(%4::Float64)::Float64 │
  │ %6 = (Base.mul_float)(2.0, 3.141592653589793)::Float64 ││╻ *
  │ %7 = (Base.Math.sqrt_llvm)(%6)::Float64 │╻ sqrt
  │ %8 = (Base.mul_float)(σ, %7)::Float64 │╻ *
  │ %9 = (Base.div_float)(%5, %8)::Float64 │╻ /
  └── return %9 │

```

The `@code_llvm` for the manual version looks sensible to me (knowing nothing about LLVM IR):

```julia
julia> @code_llvm dnorm_manual(1., 0., 1.)

; Function dnorm_manual
; Location: REPL[16]:1
define double @julia_dnorm_manual_35642(double, double, double) {
top:
; Function -; {
; Location: float.jl:397
  %3 = fsub double %0, %1
;}
; Function /; {
; Location: float.jl:401
  %4 = fdiv double %3, %2
;}
; Function abs2; {
; Location: number.jl:157
; Function *; {
; Location: float.jl:399
  %5 = fmul double %4, %4
;}}
; Function *; {
; Location: float.jl:399
  %6 = fmul double %5, -5.000000e-01
;}
  %7 = call double @julia_exp_35233(double %6)
; Function *; {
; Location: float.jl:399
  %8 = fmul double %2, 0x40040D931FF62705
;}
; Function /; {
; Location: float.jl:401
  %9 = fdiv double %7, %8
;}
  ret double %9
}

```

But for the `Normal` wrapper version there’s a bunch of stuff that I don’t quite understand but seems related maybe to the GC, along with the parts that do the actual computation (that seem similar to what I’d expect based on the `@code_llvm` for the manual version):

```julia
julia> @code_llvm dnorm(1., 0., 1.)

; Function dnorm
; Location: REPL[4]:1
define double @julia_dnorm_35643(double, double, double) {
top:
  %3 = alloca %jl_value_t addrspace(10)*, i32 4
  %gcframe = alloca %jl_value_t addrspace(10)*, i32 3
  %4 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
  call void @llvm.memset.p0i8.i32(i8* %4, i8 0, i32 24, i32 0, i1 false)
  %thread_ptr = call i8* asm "movq %fs:0, $0", "=r"()
  %ptls_i8 = getelementptr i8, i8* %thread_ptr, i64 -10920
  %ptls = bitcast i8* %ptls_i8 to %jl_value_t***
; Function Type; {
; Location: /home/dave/.julia/dev/Distributions/src/univariate/continuous/normal.jl:34
; Function Type; {
; Location: /home/dave/.julia/dev/Distributions/src/univariate/continuous/normal.jl:30
; Function macro expansion; {
; Location: /home/dave/.julia/dev/Distributions/src/utils.jl:5
; Function >; {
; Location: operators.jl:286
; Function <; {
; Location: float.jl:452
  %5 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
  %6 = bitcast %jl_value_t addrspace(10)** %5 to i64*
  store i64 2, i64* %6
  %7 = getelementptr %jl_value_t **, %jl_value_t*** %ptls, i32 0
  %8 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %9 = bitcast %jl_value_t addrspace(10) **%8 to %jl_value_t***
  %10 = load %jl_value_t **, %jl_value_t*** %7
  store %jl_value_t **%10, %jl_value_t*** %9
  %11 = bitcast %jl_value_t***%7 to %jl_value_t addrspace(10)***
  store %jl_value_t addrspace(10)**%gcframe, %jl_value_t addrspace(10)*** %11
  %12 = fcmp ogt double %2, 0.000000e+00
;}}
  br i1 %12, label %L10, label %L4

L4: ; preds = %top
; Location: /home/dave/.julia/dev/Distributions/src/utils.jl:6
  %13 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 0
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298815038800 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %13
  %14 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298792047504 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %14
  %15 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 2
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298815038832 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %15
  %16 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 3
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298792047552 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %16
  %17 = call nonnull %jl_value_t addrspace(10)* @jsys1_string_24444(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298857741968 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %3, i32 4)
; Function Type; {
; Location: boot.jl:276
  %18 = bitcast %jl_value_t*** %ptls to i8*
  %19 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %17, %jl_value_t addrspace(10)** %19
  %20 = call noalias nonnull %jl_value_t addrspace(10)* @jl_gc_pool_alloc(i8* %18, i32 1424, i32 16) #1
  %21 = bitcast %jl_value_t addrspace(10)* %20 to %jl_value_t addrspace(10)* addrspace(10)*
  %22 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(10)* %21, i64 -1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140298853852704 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)* addrspace(10)* %22
  %23 = bitcast %jl_value_t addrspace(10)* %20 to %jl_value_t addrspace(10)* addrspace(10)*
  store %jl_value_t addrspace(10)* %17, %jl_value_t addrspace(10)* addrspace(10)* %23, align 8
;}
  %24 = addrspacecast %jl_value_t addrspace(10)* %20 to %jl_value_t addrspace(12)*
  %25 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %25
  call void @jl_throw(%jl_value_t addrspace(12)* %24)
  unreachable

L10: ; preds = %top
;}}}
; Function pdf; {
; Location: /home/dave/.julia/dev/Distributions/src/univariates.jl:525
; Function normpdf; {
; Location: /home/dave/.julia/packages/StatsFuns/0W2sM/src/distrs/norm.jl:8
; Function zval; {
; Location: /home/dave/.julia/packages/StatsFuns/0W2sM/src/distrs/norm.jl:4
; Function -; {
; Location: float.jl:397
  %26 = fsub double %0, %1
;}
; Function /; {
; Location: float.jl:401
  %27 = fdiv double %26, %2
;}}
; Function normpdf; {
; Location: /home/dave/.julia/packages/StatsFuns/0W2sM/src/distrs/norm.jl:7
; Function abs2; {
; Location: number.jl:157
; Function *; {
; Location: float.jl:399
  %28 = fmul double %27, %27
;}}
; Function /; {
; Location: promotion.jl:316
; Function /; {
; Location: float.jl:401
  %29 = fmul double %28, -5.000000e-01
;}}
  %30 = call double @julia_exp_35233(double %29)
; Function *; {
; Location: promotion.jl:314
; Function *; {
; Location: float.jl:399
  %31 = fmul double %30, 0x3FD9884533D43651
;}}}
; Function /; {
; Location: float.jl:401
  %32 = fdiv double %31, %2
;}}}
  %33 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %34 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %33
  %35 = getelementptr %jl_value_t **, %jl_value_t*** %ptls, i32 0
  %36 = bitcast %jl_value_t***%35 to %jl_value_t addrspace(10)**
  store %jl_value_t addrspace(10)* %34, %jl_value_t addrspace(10)** %36
  ret double %32
}

```

So I guess I’m wondering where my intuitions have led me astray, and (possibly orthogonally) where the performance hit from using the `Normal` in `dnorm` vs. `dnorm_manual` is coming from.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 14, 2018, 7:38am UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/2 "2018-10-14T07:38:14Z")

</div>

String interpolation (used [here](https://github.com/JuliaStats/Distributions.jl/blob/7efc1d53a5ab55f3060bb34982e68e1ebce32f5a/src/utils.jl#L3-L10)) forces the allocation of the gcframe. There’s a standard trick for avoiding this problem: instead of

```julia
function mysqrt(x)
    x >= 0 || throw(ArgumentError("x must be positive, got $x"))
    sqrt(x)
end

```

do something like this:

```julia
function mysqrt(x)
    nonpos(x) = throw(ArgumentError("x must be positive, got $x"))
    x >= 0 || nonpos(x)
    sqrt(x)
end

```

By putting the error message generation in a separate function you ensure the gcframe gets allocated only in the error condition. In other people’s code (esp. older code) you may sometimes see `@noinline` in front of `nonpos`, because of course this trick fails if `nonpos` gets inlined into `mysqrt`; however, from 0.7 julia’s compiler is smart enough to figure that out on its own.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [October 14, 2018, 7:45am UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/3 "2018-10-14T07:45:05Z")

</div>

~~Is there anything that (in theory) prevents the compiler from figuring this out from the first form?~~

To answer my own question (should have done this before asking), on v1.0

```julia
using ArgCheck, BenchmarkTools

function f_plain(x)
    x ≥ 0 || throw(ArgumentError("x must be positive, got $x"))
    x + 1
end

function f_wrap(x)
    nonpos(x) = throw(ArgumentError("x must be positive, got $x"))
    x ≥ 0 || nonpos(x)
    x + 1
end

x = 2.0
@btime f_plain($x)
@btime f_wrap($x)

```

benchmarks as

```julia
  3.162 ns (0 allocations: 0 bytes)
  2.026 ns (0 allocations: 0 bytes)

```

but on current master,

```julia
julia> VERSION
v"1.1.0-DEV.444"

julia> @btime f_plain($x)
  0.029 ns (0 allocations: 0 bytes)
3.0

julia> @btime f_wrap($x)
  0.029 ns (0 allocations: 0 bytes)
3.0

```

---

<div class="post-metadata">

**Author:** ![bennedich](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bennedich/32/4894_2.png) [@bennedich](https://discourse.julialang.org/u/bennedich)\
**Post date:** [October 14, 2018, 8:07am UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/4 "2018-10-14T08:07:04Z")

</div>

0.029 ns is around 0.1 CPU cycles per call, how is that even possible?

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [October 14, 2018, 8:12am UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/5 "2018-10-14T08:12:30Z")

</div>

SIMD I guess (may not be relevant for more complex things).

Or, possibly, magic. Some recent improvements are spooky, I just got a 3x speedup in some MCMC code last week (I keep on `master` for production, can always switch to stable when things go south) for unknown reasons. Ain’t complaining though.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 14, 2018, 8:19am UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/6 "2018-10-14T08:19:12Z")

</div>

Actually, I’m not sure this has been optimized on master, check the `@code_llvm` and you’ll see the gcframe is right there at the top.

I’m guessing there’s some purity analysis that helps the compiler realize the function does the same thing on each iteration and thus replaces the result with a constant. I.e., this is basically breaking BenchmarkTools.

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [October 14, 2018, 12:09pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/7 "2018-10-14T12:09:43Z")

</div>

Hmm I have this on my 8 Core Xeon

```julia
julia> VERSION
v"1.1.0-DEV.301"

julia> @btime f_plain($x)
  2.662 ns (0 allocations: 0 bytes)
3.0

julia> @btime f_wrap($x)
  1.337 ns (0 allocations: 0 bytes)
3.0

julia> peakflops() / 1e9
171.03434946978282

```

I need to update to 444 😉

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [October 14, 2018, 12:17pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/8 "2018-10-14T12:17:57Z")

</div>

OK confirmed with 447:

```julia
julia> VERSION
v"1.1.0-DEV.447"

julia> @btime f_plain($x)
  0.018 ns (0 allocations: 0 bytes)
3.0

julia> @btime f_wrap($x)
  0.018 ns (0 allocations: 0 bytes)
3.0

```

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [October 14, 2018, 12:43pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/9 "2018-10-14T12:43:14Z")

</div>

As @tim.holy said, everything sub-nanosecond tends to be an artifact of something breaking `BenchmarkTools`.

More generally, `@btime` should not be trusted below 100ns. If you want to benchmark a cheap function, make your own loop around it, make your own decisions whether you want to benchmark inlined or `@noinline` context.

It is best to benchmark in semi-realistic context: Can this be SIMD? What execution units do you use? Latency can become more important than throughput if an unpredictable branch depends on your result. If inlined, then it is important what registers get spilled.

Some of that stuff is near impossible to predict, even from `@code_native`, and if your planned use is to broadcast your function against an array, benchmark that instead of a single evaluation.

Postscript:

```julia
julia> x=rand(1000); y=similar(x);

julia> @btime broadcast!($f_wrap, $y, $x);
  906.415 ns (0 allocations: 0 bytes)

julia> @btime broadcast!($f_plain, $y, $x);
  906.829 ns (0 allocations: 0 bytes)

julia> z=0.1;

julia> @btime f_plain($z);
  4.407 ns (0 allocations: 0 bytes)

julia> @btime f_wrap($z);
  2.211 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![essenciary](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/essenciary/32/210469_2.png) [@essenciary](https://discourse.julialang.org/u/essenciary)\
**Post date:** [October 14, 2018, 4:29pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/10 "2018-10-14T16:29:57Z")

</div>

Any place where we can read about more such standard tricks?

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 14, 2018, 4:32pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/11 "2018-10-14T16:32:48Z")

</div>

I usually curl up with a good `$JULIA_SOURCE_CODE/base/sourcefile.jl` after dinner 😄. Not sure if there’s another good place to find such tricks.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [October 14, 2018, 6:01pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/12 "2018-10-14T18:01:22Z")

</div>

It would be _really_ good to get an optimization pass that does this transformation automatically.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [October 14, 2018, 6:17pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/13 "2018-10-14T18:17:39Z")

</div>

Indeed it would. It may not be entirely trivial since it’s essentially an escape analysis (for the gcframe), but it’s a pretty special case.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [October 14, 2018, 8:28pm UTC](https://discourse.julialang.org/t/understanding-performance-using-btime-and-code-warntype-code-llvm-etc/16293/14 "2018-10-14T20:28:36Z")

</div>

I suspect that special casing code that throws and automatically factoring it out into a separate function call would be effective enough as a heuristic.
