From 802a9ccb5218dd0c8c95ebca1a7c39f89ce8b2e7 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Wed, 22 May 2019 00:49:36 +0200 Subject: [PATCH 01/10] it looks like I got dot access working \o/ --- Project.toml | 8 +++++--- README.md | 4 +--- src/OOPMacroImpl.jl | 13 ++++++++++++- test/basic.jl | 4 ++++ test/inheritence.jl | 8 ++++++++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 3f86260..47c380e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,15 @@ name = "OOPMacro" uuid = "ff64c594-4e62-11e9-31a6-8538154e3622" authors = ["Shih-Ming Wang ", "Marius Kruger "] -version = "0.3.0" +version = "0.4.0" -[compat] -julia = ">= 0.7" +[deps] [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Test"] + +[compat] +julia = ">= 0.7" diff --git a/README.md b/README.md index d8ea598..ae296de 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,5 @@ cvalue = c.cfield # Future Work - write unit test for each function in clsUtil and OOPMacroImpl -- override getproperty() to make more natural usage of - 'methods' -- maybe don't require manually setting the self arg when declaring methods; rathre specify @static if it is not a object method +- maybe don't require manually setting the self arg when declaring methods; rather specify @static if it is not a object method - Type generic parameter ?? diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index cd71cc4..db5ea40 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -79,10 +79,21 @@ macro class(ClsName, Cbody) mutable struct $ClsName $(join(fields,"\n")) """ * cons_str * """ + end""" + + # this allows calling functions on the class.. + dotAccStr = """ + function Base.getproperty(self::$ClsName, name::Symbol) + if name ∈ fieldnames(typeof(self)) + getfield(self, name) + else + (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) + end end """ + # Escape here because we want ClsName and the methods be defined in user scope instead of OOPMacro module scope. - esc(Expr(:block, Meta.parse(clsDefStr), values(methods)...)) + esc(Expr(:block, Meta.parse(clsDefStr), Meta.parse(dotAccStr), values(methods)...)) end macro super(ParentClsName, FCall) diff --git a/test/basic.jl b/test/basic.jl index 9227c81..a241119 100644 --- a/test/basic.jl +++ b/test/basic.jl @@ -30,6 +30,7 @@ s = SimpleCls(0,1,2) @test s.field2 == 2 @test fun0(s, 2.) == 2 @test fun1(s, 2., 3) == 3 +@test s.fun1(2., 3) == 3 @test fun2(s, 2.) == 4 @test fun2(s, 2) == 4 @test_throws(MethodError, fun2(s,"a")) @@ -41,7 +42,10 @@ s = SimpleCls(0,1,2) end s1 = SimpleCls1(0) @test fun0(s1, 1) == 2 +@test s1.fun0(1) == 2 @test fun0(s1, 1, 2) == 3 +@test s1.fun0(1, 2) == 3 @test fun1(s1, 1) == 4 @test fun1(s1, 1, 2) == 5 @test fun1(s1, 1, 2, z=3) == 6 +@test s1.fun1(1, 2, z=3) == 6 diff --git a/test/inheritence.jl b/test/inheritence.jl index 38eb8a1..f446d00 100644 --- a/test/inheritence.jl +++ b/test/inheritence.jl @@ -31,13 +31,21 @@ pvalue = p.pfield pvalue2 = c.pfield2 cvalue = c.cfield @test pfun(p) == pvalue +@test p.pfun() == pvalue @test pfun(c) == cvalue +@test c.pfun() == cvalue @test pfunAdd(p,1) == pvalue + 1 +@test p.pfunAdd(1) == pvalue + 1 @test pfunAdd(c,1) == cvalue + 1 +@test c.pfunAdd(1) == cvalue + 1 @test_throws(MethodError, pfunAdd(c,"a")) @test cfunSuper(c) == pvalue +@test c.cfunSuper() == pvalue @test cfunAddSuper(c,1) == pvalue+1 +@test c.cfunAddSuper(1) == pvalue+1 @test cfunSuper2(c) == pvalue2 +@test c.cfunSuper2() == pvalue2 @test cfunAddSuper2(c,1) == pvalue2+1 +@test c.cfunAddSuper2(1) == pvalue2+1 From f66297436453105e55bfcabf0792f800c64198c2 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Wed, 22 May 2019 21:58:29 +0200 Subject: [PATCH 02/10] we know they type so don't call typeof --- src/OOPMacroImpl.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index db5ea40..27e191a 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -84,7 +84,7 @@ macro class(ClsName, Cbody) # this allows calling functions on the class.. dotAccStr = """ function Base.getproperty(self::$ClsName, name::Symbol) - if name ∈ fieldnames(typeof(self)) + if name ∈ fieldnames($ClsName) getfield(self, name) else (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) From 024a0d720b02732c2bf93bfa526912c080fe7d65 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Wed, 22 May 2019 23:35:58 +0200 Subject: [PATCH 03/10] make the dotoperator optional and add tests --- src/OOPMacroImpl.jl | 55 +++++++++++++++++++++++++++++---------------- src/clsUtil.jl | 2 ++ test/dotoperator.jl | 22 ++++++++++++++++++ test/runtests.jl | 1 + 4 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 test/dotoperator.jl diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index 27e191a..84e142b 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -4,22 +4,35 @@ include("clsUtil.jl") #= ClsMethods = Dict{Symbol, Dict{Expr, Expr}}() =# ClsMethods = Dict(:Any=>Dict{Expr, Expr}()) ClsFields = Dict(:Any=>Vector{Expr}()) +validOptions = (:nodotoperator,) +macro class(args...) + if length(args) < 2 + error("At least a class name and body must be specified.") + end + options = args[1:end-2] + clsName = args[end-1] + cBody = args[end] + for o in options + if o ∉ validOptions + error("$o is not a valid option. Valid options are: $(join(validOptions, ", "))") + end + end + supportDotOperator = :nodotoperator ∉ options -macro class(ClsName, Cbody) - ClsName, ParentClsNameLst = getCAndP(ClsName) - AbsClsName = getAbstractCls(ClsName) + clsName, ParentClsNameLst = getCAndP(clsName) + AbsClsName = getAbstractCls(clsName) AbsParentClsName = getAbstractCls(ParentClsNameLst) - ClsFields[ClsName] = fields = copyFields(ParentClsNameLst, ClsFields) - ClsMethods[ClsName] = methods = Dict{Expr,Expr}() + ClsFields[clsName] = fields = copyFields(ParentClsNameLst, ClsFields) + ClsMethods[clsName] = methods = Dict{Expr,Expr}() cons = Any[] hasInit = false # record fields and methods separately - for (i, block) in enumerate(Cbody.args) + for (i, block) in enumerate(cBody.args) if isa(block, Symbol) union!(fields, [:($block::Any)]) elseif isa(block, LineNumberNode) @@ -30,19 +43,19 @@ macro class(ClsName, Cbody) continue elseif block.head == :(=) || block.head == :function fname = getFnName(block, withoutGeneric=true) - if fname == ClsName + if fname == clsName append!(cons, [block]) elseif fname == :__init__ hasInit = true - setFnName!(block, ClsName) - self = findFnSelfArgNameSymbol(block, ClsName) + setFnName!(block, clsName) + self = findFnSelfArgNameSymbol(block, clsName) deleteFnSelf!(block) - prepend!(block.args[2].args, [:($self = $ClsName(()))]) + prepend!(block.args[2].args, [:($self = $clsName(()))]) append!(block.args[2].args, [:($self)]) append!(cons, [block]) else fn = copy(block) - setFnSelfArgType!(fn, ClsName) + setFnSelfArgType!(fn, clsName) methods[findFnCall(fn)] = fn end else @@ -55,12 +68,12 @@ macro class(ClsName, Cbody) for parent in ParentClsNameLst for pfn in values(ClsMethods[parent]) fn = copy(pfn) - setFnSelfArgType!(fn, ClsName) + setFnSelfArgType!(fn, clsName) fnCall = findFnCall(fn) if haskey(methods, fnCall) fName = getFnName(fn, withoutGeneric=true) if !(fnCall in ClsFnCalls) - error("Ambiguious Function Definition: Multiple definition of function $fName while $ClsName does not overwtie this function!!") + error("Ambiguious Function Definition: Multiple definition of function $fName while $clsName does not overwtie this function!!") end setFnName!(fn, Symbol(string("super_", parent, fName)), withoutGeneric=true) methods[fnCall] = fn @@ -72,28 +85,32 @@ macro class(ClsName, Cbody) cons_str = join(cons,"\n") * "\n" if hasInit - cons_str *= "$ClsName(::Tuple{}) = new()\n" + cons_str *= "$clsName(::Tuple{}) = new()\n" end clsDefStr = """ - mutable struct $ClsName + mutable struct $clsName $(join(fields,"\n")) """ * cons_str * """ end""" # this allows calling functions on the class.. dotAccStr = """ - function Base.getproperty(self::$ClsName, name::Symbol) - if name ∈ fieldnames($ClsName) + function Base.getproperty(self::$clsName, name::Symbol) + if name ∈ fieldnames($clsName) getfield(self, name) else (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) end end """ + blockSections = [Meta.parse(clsDefStr), values(methods)...] + if supportDotOperator + push!(blockSections, Meta.parse(dotAccStr)) + end - # Escape here because we want ClsName and the methods be defined in user scope instead of OOPMacro module scope. - esc(Expr(:block, Meta.parse(clsDefStr), Meta.parse(dotAccStr), values(methods)...)) + # Escape here because we want clsName and the methods be defined in user scope instead of OOPMacro module scope. + esc(Expr(:block, blockSections...)) end macro super(ParentClsName, FCall) diff --git a/src/clsUtil.jl b/src/clsUtil.jl index b218bd0..67d8375 100644 --- a/src/clsUtil.jl +++ b/src/clsUtil.jl @@ -1,3 +1,5 @@ + +""" Get the class name and parents """ function getCAndP(cls) if isa(cls, Symbol) C, P = cls, [:Any] diff --git a/test/dotoperator.jl b/test/dotoperator.jl new file mode 100644 index 0000000..cdf7d84 --- /dev/null +++ b/test/dotoperator.jl @@ -0,0 +1,22 @@ +using Test + +# test invalid option validation: +@test_throws(LoadError, @macroexpand @class invalidoption MyCls begin end) + +@class BasicDotOpr begin + field0::Int + fun0(self::SimpleCls, x) = self.field0 + x +end + +@class nodotoperator BasicNoDotOpr begin + field0::Int + fun0(self::SimpleCls, x) = self.field0 + x +end + +bdo = BasicDotOpr(1) +@test fun0(bdo, 1) == 2 +@test bdo.fun0(1) == 2 + +bndo = BasicDotOpr(1) +@test_throws(UndefVarError, fun0(nbdo, 1)) +@test bndo.fun0(1) == 2 diff --git a/test/runtests.jl b/test/runtests.jl index ec27401..2e8b4ec 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,3 +5,4 @@ include("fnUtil.jl") include("basic.jl") include("constructor.jl") include("inheritence.jl") +include("dotoperator.jl") From 2aeb07f69d81bc2bdeb0649c8b45d621f0123106 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Thu, 23 May 2019 23:17:15 +0200 Subject: [PATCH 04/10] split up in test sets --- test/dotoperator.jl | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/dotoperator.jl b/test/dotoperator.jl index cdf7d84..6c09f31 100644 --- a/test/dotoperator.jl +++ b/test/dotoperator.jl @@ -1,7 +1,9 @@ using Test -# test invalid option validation: -@test_throws(LoadError, @macroexpand @class invalidoption MyCls begin end) +@testset "invalid option validation" begin + @test_throws(LoadError, @macroexpand @class invalidoption MyCls begin end) +end + @class BasicDotOpr begin field0::Int @@ -13,10 +15,14 @@ end fun0(self::SimpleCls, x) = self.field0 + x end -bdo = BasicDotOpr(1) -@test fun0(bdo, 1) == 2 -@test bdo.fun0(1) == 2 +@testset "basic test with dot operator" begin + bdo = BasicDotOpr(1) + @test fun0(bdo, 1) == 2 + @test bdo.fun0(1) == 2 +end -bndo = BasicDotOpr(1) -@test_throws(UndefVarError, fun0(nbdo, 1)) -@test bndo.fun0(1) == 2 +@testset "basic test without dot operator" begin + bndo = BasicDotOpr(1) + @test_throws(UndefVarError, fun0(nbdo, 1)) + @test bndo.fun0(1) == 2 +end From 707758ad3d63df8aa9ba4fe987a6f83bab49c7ed Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Fri, 24 May 2019 01:21:36 +0200 Subject: [PATCH 05/10] =?UTF-8?q?Initial=20benchmark=20shows=20field=20acc?= =?UTF-8?q?ess=20regresses=20from=203.921=20ns=20to=201.162=20=CE=BCs=20i.?= =?UTF-8?q?e=20(+29525.86%=20=3D>=20regression)=20:'(?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Project.toml | 10 +++++----- test/dotoperator.jl | 34 ++++++++++++++++++++-------------- test/dotoperator_benchmark.jl | 30 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 test/dotoperator_benchmark.jl diff --git a/Project.toml b/Project.toml index a317948..0c046ea 100644 --- a/Project.toml +++ b/Project.toml @@ -3,13 +3,13 @@ uuid = "90c472d1-064c-5c63-af2e-229f1fdb5f26" authors = ["Shih-Ming Wang ", "Marius Kruger "] version = "0.4.0" -[deps] +[compat] +julia = ">= 0.7" [extras] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] - -[compat] -julia = ">= 0.7" +test = ["Test", "BenchmarkTools", "Statistics"] diff --git a/test/dotoperator.jl b/test/dotoperator.jl index 6c09f31..9d767cf 100644 --- a/test/dotoperator.jl +++ b/test/dotoperator.jl @@ -1,10 +1,5 @@ using Test -@testset "invalid option validation" begin - @test_throws(LoadError, @macroexpand @class invalidoption MyCls begin end) -end - - @class BasicDotOpr begin field0::Int fun0(self::SimpleCls, x) = self.field0 + x @@ -15,14 +10,25 @@ end fun0(self::SimpleCls, x) = self.field0 + x end -@testset "basic test with dot operator" begin - bdo = BasicDotOpr(1) - @test fun0(bdo, 1) == 2 - @test bdo.fun0(1) == 2 -end +@testset "dot operator tests" begin + + @testset "invalid option validation" begin + @test_throws(LoadError, @macroexpand @class invalidoption MyCls begin end) + end + + @testset "tests with dot operator" begin + @testset "basic test" begin + bdo = BasicDotOpr(1) + @test fun0(bdo, 1) == 2 + @test bdo.fun0(1) == 2 + end + end -@testset "basic test without dot operator" begin - bndo = BasicDotOpr(1) - @test_throws(UndefVarError, fun0(nbdo, 1)) - @test bndo.fun0(1) == 2 + @testset "tests without dot operator" begin + @testset "basic test" begin + bndo = BasicDotOpr(1) + @test_throws(UndefVarError, fun0(nbdo, 1)) + @test bndo.fun0(1) == 2 + end + end end diff --git a/test/dotoperator_benchmark.jl b/test/dotoperator_benchmark.jl new file mode 100644 index 0000000..cd4a76b --- /dev/null +++ b/test/dotoperator_benchmark.jl @@ -0,0 +1,30 @@ +using BenchmarkTools +using Statistics + +if !isdefined(Main, :B1DotOpr) + @class B1DotOpr begin + field1::Int + fun1(self::SimpleCls, x) = self.field0 + x + end +end +if !isdefined(Main, :B1NoDotOpr) + @class nodotoperator B1NoDotOpr begin + field1::Int + fun1(self::SimpleCls, x) = self.field0 + x + end +end + +bg = BenchmarkGroup() +bg["DotOpr"] = BenchmarkGroup() +bg["NoDotOpr"] = BenchmarkGroup() + +bg["DotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1DotOpr(rand(Int))) +bg["NoDotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1NoDotOpr(rand(Int))) + +tune!(bg) +results = run(bg, verbose = true, seconds = 1) + +med = median(results) +println(med) +j = judge(med["DotOpr"]["get_field1"], med["NoDotOpr"]["get_field1"]) +println(j) From 6538d44cf0757f0edf5083c927e21d68b2762b77 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Sun, 26 May 2019 22:18:51 +0200 Subject: [PATCH 06/10] using try helps a lot, but I still don't know why using try is 40 times slower :'( TrialJudgement(+4188.17% => regression) --- src/OOPMacroImpl.jl | 8 ++++---- test/dotoperator_benchmark.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index 84e142b..b032748 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -97,10 +97,10 @@ macro class(args...) # this allows calling functions on the class.. dotAccStr = """ function Base.getproperty(self::$clsName, name::Symbol) - if name ∈ fieldnames($clsName) - getfield(self, name) - else - (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) + try + getfield(self, name) + catch + (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) end end """ diff --git a/test/dotoperator_benchmark.jl b/test/dotoperator_benchmark.jl index cd4a76b..e0f89c3 100644 --- a/test/dotoperator_benchmark.jl +++ b/test/dotoperator_benchmark.jl @@ -22,7 +22,7 @@ bg["DotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1DotOpr(rand(In bg["NoDotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1NoDotOpr(rand(Int))) tune!(bg) -results = run(bg, verbose = true, seconds = 1) +results = run(bg, verbose = true, seconds = 5) med = median(results) println(med) From 05cef30481f59c1ff8ea11b0f5c01e2a34bcb265 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Fri, 31 May 2019 00:11:12 +0200 Subject: [PATCH 07/10] got the timing to be on par! --- src/OOPMacroImpl.jl | 17 +++++++++-------- test/dotoperator.jl | 12 +++++++++--- test/dotoperator_benchmark.jl | 4 ++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index b032748..8604997 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -95,15 +95,16 @@ macro class(args...) end""" # this allows calling functions on the class.. + clsFnNameList = join(map(fn->":$(getFnName(fn, withoutGeneric=true)),", collect(values(methods))),"") dotAccStr = """ - function Base.getproperty(self::$clsName, name::Symbol) - try - getfield(self, name) - catch - (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) - end - end - """ + function Base.getproperty(self::$clsName, name::Symbol) + if isdefined(self, name) || name ∉ ($clsFnNameList) + getfield(self, name) + else + (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) + end + end + """ blockSections = [Meta.parse(clsDefStr), values(methods)...] if supportDotOperator push!(blockSections, Meta.parse(dotAccStr)) diff --git a/test/dotoperator.jl b/test/dotoperator.jl index 9d767cf..a1a5cb2 100644 --- a/test/dotoperator.jl +++ b/test/dotoperator.jl @@ -19,6 +19,9 @@ end @testset "tests with dot operator" begin @testset "basic test" begin bdo = BasicDotOpr(1) + @code_llvm bdo.field0 + @time bdo.field0 + @test_throws(ErrorException, bdo.invalidfield) @test fun0(bdo, 1) == 2 @test bdo.fun0(1) == 2 end @@ -26,9 +29,12 @@ end @testset "tests without dot operator" begin @testset "basic test" begin - bndo = BasicDotOpr(1) - @test_throws(UndefVarError, fun0(nbdo, 1)) - @test bndo.fun0(1) == 2 + bndo = BasicNoDotOpr(1) + @code_llvm bndo.field0 + @time bndo.field0 + @test_throws(ErrorException, bndo.invalidfield) + @test fun0(bndo, 1) == 2 + @test_throws(ErrorException, bndo.fun0(1)) end end end diff --git a/test/dotoperator_benchmark.jl b/test/dotoperator_benchmark.jl index e0f89c3..414f28b 100644 --- a/test/dotoperator_benchmark.jl +++ b/test/dotoperator_benchmark.jl @@ -4,13 +4,13 @@ using Statistics if !isdefined(Main, :B1DotOpr) @class B1DotOpr begin field1::Int - fun1(self::SimpleCls, x) = self.field0 + x + fun1(self::SimpleCls, x) = self.field1 + x end end if !isdefined(Main, :B1NoDotOpr) @class nodotoperator B1NoDotOpr begin field1::Int - fun1(self::SimpleCls, x) = self.field0 + x + fun1(self::SimpleCls, x) = self.field1 + x end end From 65e3f6cb1d3102398461731bd8194185e5361ce6 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Fri, 31 May 2019 00:23:46 +0200 Subject: [PATCH 08/10] now the dot operator gets optomized pretty quick "NoDotOpr" => 1-element BenchmarkTools.BenchmarkGroup: tags: [] "get_field1" => TrialEstimate(3.921 ns) "DotOpr" => 1-element BenchmarkTools.BenchmarkGroup: tags: [] "get_field1" => TrialEstimate(3.921 ns) TrialJudgement(+0.00% => invariant) bdo = BasicDotOpr(1) = BasicDotOpr(1) 0.023392 seconds (14.88 k allocations: 793.028 KiB) 0.000007 seconds (4 allocations: 160 bytes) 0.000006 seconds (4 allocations: 160 bytes) bndo = BasicNoDotOpr(1) = BasicNoDotOpr(1) 0.000006 seconds (4 allocations: 160 bytes) 0.000003 seconds (4 allocations: 160 bytes) 0.000003 seconds (4 allocations: 160 bytes) --- test/dotoperator.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/dotoperator.jl b/test/dotoperator.jl index a1a5cb2..6239694 100644 --- a/test/dotoperator.jl +++ b/test/dotoperator.jl @@ -18,8 +18,9 @@ end @testset "tests with dot operator" begin @testset "basic test" begin - bdo = BasicDotOpr(1) - @code_llvm bdo.field0 + @show bdo = BasicDotOpr(1) + @time bdo.field0 + @time bdo.field0 @time bdo.field0 @test_throws(ErrorException, bdo.invalidfield) @test fun0(bdo, 1) == 2 @@ -29,8 +30,9 @@ end @testset "tests without dot operator" begin @testset "basic test" begin - bndo = BasicNoDotOpr(1) - @code_llvm bndo.field0 + @show bndo = BasicNoDotOpr(1) + @time bndo.field0 + @time bndo.field0 @time bndo.field0 @test_throws(ErrorException, bndo.invalidfield) @test fun0(bndo, 1) == 2 From bf2ef1f1bec1899ac6ecae239f77d758887f02c8 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Wed, 5 Jun 2019 01:20:55 +0200 Subject: [PATCH 09/10] tried caching the functions but it still looks pretty slow :'( --- src/OOPMacroImpl.jl | 26 ++++++++++++++++++-------- test/dotoperator.jl | 6 ++++++ test/dotoperator_benchmark.jl | 27 ++++++++++++++++++++++----- test/runtests.jl | 1 + 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index 8604997..08318d1 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -6,6 +6,8 @@ ClsMethods = Dict(:Any=>Dict{Expr, Expr}()) ClsFields = Dict(:Any=>Vector{Expr}()) validOptions = (:nodotoperator,) +OOPMacroModule=@__MODULE__ +clsFnDefDict=WeakKeyDict{Any,Dict{Symbol,Function}}() macro class(args...) if length(args) < 2 error("At least a class name and body must be specified.") @@ -27,7 +29,6 @@ macro class(args...) ClsFields[clsName] = fields = copyFields(ParentClsNameLst, ClsFields) ClsMethods[clsName] = methods = Dict{Expr,Expr}() - cons = Any[] hasInit = false @@ -63,7 +64,6 @@ macro class(args...) end end - ClsFnCalls = Set(keys(methods)) for parent in ParentClsNameLst for pfn in values(ClsMethods[parent]) @@ -73,7 +73,7 @@ macro class(args...) if haskey(methods, fnCall) fName = getFnName(fn, withoutGeneric=true) if !(fnCall in ClsFnCalls) - error("Ambiguious Function Definition: Multiple definition of function $fName while $clsName does not overwtie this function!!") + error("Ambiguious Function Definition: Multiple definition of function $fName while $clsName does not overwrite this function!!") end setFnName!(fn, Symbol(string("super_", parent, fName)), withoutGeneric=true) methods[fnCall] = fn @@ -95,13 +95,23 @@ macro class(args...) end""" # this allows calling functions on the class.. - clsFnNameList = join(map(fn->":$(getFnName(fn, withoutGeneric=true)),", collect(values(methods))),"") + clsFnNames = map(fn->"$(getFnName(fn, withoutGeneric=true))", collect(values(methods))) + clsFnNameList = join(map(name->":$name,", clsFnNames),"") dotAccStr = """ - function Base.getproperty(self::$clsName, name::Symbol) - if isdefined(self, name) || name ∉ ($clsFnNameList) - getfield(self, name) + function Base.getproperty(self::$clsName, nameSymbol::Symbol) + if isdefined(self, nameSymbol) || nameSymbol ∉ ($clsFnNameList) + getfield(self, nameSymbol) else - (args...; kwargs...)->eval(:(\$name(\$self, \$args...; \$kwargs...))) + if haskey($(OOPMacroModule).clsFnDefDict, self) + fnDict=$(OOPMacroModule).clsFnDefDict[self] + else + fnDict=$(OOPMacroModule).clsFnDefDict[self] = Dict{Symbol,Function}() + end + if haskey(fnDict, nameSymbol) + fnDict[nameSymbol] + else + fnDict[nameSymbol]=(args...; kwargs...)->eval(:(\$nameSymbol(\$self, \$args...; \$kwargs...))) + end end end """ diff --git a/test/dotoperator.jl b/test/dotoperator.jl index 6239694..a0f9e4d 100644 --- a/test/dotoperator.jl +++ b/test/dotoperator.jl @@ -24,7 +24,13 @@ end @time bdo.field0 @test_throws(ErrorException, bdo.invalidfield) @test fun0(bdo, 1) == 2 + @time fun0(bdo, 1) == 2 + @time fun0(bdo, 1) == 2 + @time fun0(bdo, 1) == 2 @test bdo.fun0(1) == 2 + @time bdo.fun0(1) == 2 + @time bdo.fun0(1) == 2 + @time bdo.fun0(1) == 2 end end diff --git a/test/dotoperator_benchmark.jl b/test/dotoperator_benchmark.jl index 414f28b..4631229 100644 --- a/test/dotoperator_benchmark.jl +++ b/test/dotoperator_benchmark.jl @@ -1,3 +1,4 @@ +using Test using BenchmarkTools using Statistics @@ -21,10 +22,26 @@ bg["NoDotOpr"] = BenchmarkGroup() bg["DotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1DotOpr(rand(Int))) bg["NoDotOpr"]["get_field1"] = @benchmarkable o.field1 setup=(o = B1NoDotOpr(rand(Int))) +bg["DotOpr"]["call_normal_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1DotOpr(rand(Int))) +bg["NoDotOpr"]["call_normal_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int))) + +bg["DotOpr"]["call_fun1"] = @benchmarkable o.fun1(1) setup=(o = B1DotOpr(rand(Int))) +bg["NoDotOpr"]["call_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int))) + +bg["DotOpr"]["call_warm_fun1"] = @benchmarkable o.fun1(1) setup=(o = B1DotOpr(rand(Int)); o.fun1(1)) +bg["NoDotOpr"]["call_warm_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int)); fun1(o, 1)) + tune!(bg) -results = run(bg, verbose = true, seconds = 5) +results = run(bg, verbose = true, seconds = 2) -med = median(results) -println(med) -j = judge(med["DotOpr"]["get_field1"], med["NoDotOpr"]["get_field1"]) -println(j) +@testset "tests if there are regressions" begin + for t in ("get_field1", "call_normal_fun1", "call_fun1", "call_warm_fun1") + @testset "regressions in $t" begin + med = median(results) + #println(t, med) + j = judge(med["DotOpr"][t], med["NoDotOpr"][t]) + println(t, ": ", j) + @test !isregression(j) + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 2e8b4ec..f34edbe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,3 +6,4 @@ include("basic.jl") include("constructor.jl") include("inheritence.jl") include("dotoperator.jl") +include("dotoperator_benchmark.jl") From 6bd891004c1925c474d4649d2cd3e493ee257144 Mon Sep 17 00:00:00 2001 From: Marius Kruger Date: Wed, 6 May 2020 01:43:49 +0200 Subject: [PATCH 10/10] some more wip --- src/OOPMacroImpl.jl | 45 +++++++++++++++++++---------------- test/basic.jl | 6 +++++ test/dotoperator_benchmark.jl | 17 ++++++++++--- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/OOPMacroImpl.jl b/src/OOPMacroImpl.jl index 08318d1..59560e5 100644 --- a/src/OOPMacroImpl.jl +++ b/src/OOPMacroImpl.jl @@ -69,7 +69,7 @@ macro class(args...) for pfn in values(ClsMethods[parent]) fn = copy(pfn) setFnSelfArgType!(fn, clsName) - fnCall = findFnCall(fn) + fnCall = findFncons_strCall(fn) if haskey(methods, fnCall) fName = getFnName(fn, withoutGeneric=true) if !(fnCall in ClsFnCalls) @@ -84,40 +84,45 @@ macro class(args...) end cons_str = join(cons,"\n") * "\n" + @show hasInit if hasInit cons_str *= "$clsName(::Tuple{}) = new()\n" end - +# println("cons_str ",con_str) clsDefStr = """ mutable struct $clsName $(join(fields,"\n")) """ * cons_str * """ + # fun1::Function end""" - +println("clsDefStr ",clsDefStr) # this allows calling functions on the class.. clsFnNames = map(fn->"$(getFnName(fn, withoutGeneric=true))", collect(values(methods))) clsFnNameList = join(map(name->":$name,", clsFnNames),"") - dotAccStr = """ - function Base.getproperty(self::$clsName, nameSymbol::Symbol) - if isdefined(self, nameSymbol) || nameSymbol ∉ ($clsFnNameList) - getfield(self, nameSymbol) - else - if haskey($(OOPMacroModule).clsFnDefDict, self) - fnDict=$(OOPMacroModule).clsFnDefDict[self] - else - fnDict=$(OOPMacroModule).clsFnDefDict[self] = Dict{Symbol,Function}() - end - if haskey(fnDict, nameSymbol) - fnDict[nameSymbol] - else - fnDict[nameSymbol]=(args...; kwargs...)->eval(:(\$nameSymbol(\$self, \$args...; \$kwargs...))) - end - end + # dotAccStr = """ + # function Base.getproperty(self::$clsName, nameSymbol::Symbol) + # if isdefined(self, nameSymbol) #|| nameSymbol ∉ ($clsFnNameList) + # getfield(self, nameSymbol) + # else + # $(OOPMacroModule).clsFnDefDict[self][nameSymbol] + # end + # end + # """ + dotAccStr2 = """ + function __initOOPMacroFunctions(self::$clsName) + #fnDict=$(OOPMacroModule).clsFnDefDict[self] = Dict{Symbol,Function}() + #for nameSymbol in ($clsFnNameList) + #self.fun1=(args...; kwargs...)->eval(:(\$nameSymbol(\$self, \$args...; \$kwargs...))) + self.fun1=(args...; kwargs...)->eval(:(fun1(\$self, \$args...; \$kwargs...))) + #end + self end """ blockSections = [Meta.parse(clsDefStr), values(methods)...] if supportDotOperator - push!(blockSections, Meta.parse(dotAccStr)) + push!(blockSections, #Meta.parse(dotAccStr), + Meta.parse(dotAccStr2) + ) end # Escape here because we want clsName and the methods be defined in user scope instead of OOPMacro module scope. diff --git a/test/basic.jl b/test/basic.jl index a241119..4b49077 100644 --- a/test/basic.jl +++ b/test/basic.jl @@ -4,6 +4,9 @@ using Test field0 field1::Int field2::Int + SimpleCls(field0, field1::Int, field2::Int) = begin + self = __initOOPMacroFunctions(new(field0, field1, field2)) + end #= Supports different style of function declaration =# fun0(self::SimpleCls, x) = self.field0 + x @@ -37,6 +40,9 @@ s = SimpleCls(0,1,2) @class SimpleCls1 begin field0::Int + SimpleCls1(field0::Int) = begin + self = __initOOPMacroFunctions(new(field0)) + end fun0(self, x, y=1) = self.field0 + x + y fun1(self, x, y=1; z=2) = self.field0 + x + y + z end diff --git a/test/dotoperator_benchmark.jl b/test/dotoperator_benchmark.jl index 4631229..c920c6b 100644 --- a/test/dotoperator_benchmark.jl +++ b/test/dotoperator_benchmark.jl @@ -5,13 +5,22 @@ using Statistics if !isdefined(Main, :B1DotOpr) @class B1DotOpr begin field1::Int + fun1#::Function fun1(self::SimpleCls, x) = self.field1 + x + B1DotOpr(field1::Int) = begin + self = __initOOPMacroFunctions(new(field1,nothing)) + #self.field1=field1 + end end end if !isdefined(Main, :B1NoDotOpr) @class nodotoperator B1NoDotOpr begin field1::Int fun1(self::SimpleCls, x) = self.field1 + x + # B1NoDotOpr(field1::Int) = begin + # self = new() + # self.field1=field1 + # end end end @@ -28,14 +37,16 @@ bg["NoDotOpr"]["call_normal_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDo bg["DotOpr"]["call_fun1"] = @benchmarkable o.fun1(1) setup=(o = B1DotOpr(rand(Int))) bg["NoDotOpr"]["call_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int))) -bg["DotOpr"]["call_warm_fun1"] = @benchmarkable o.fun1(1) setup=(o = B1DotOpr(rand(Int)); o.fun1(1)) -bg["NoDotOpr"]["call_warm_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int)); fun1(o, 1)) +# bg["DotOpr"]["call_warm_fun1"] = @benchmarkable o.fun1(1) setup=(o = B1DotOpr(rand(Int)); o.fun1(1)) +# bg["NoDotOpr"]["call_warm_fun1"] = @benchmarkable fun1(o, 1) setup=(o = B1NoDotOpr(rand(Int)); fun1(o, 1)) tune!(bg) results = run(bg, verbose = true, seconds = 2) @testset "tests if there are regressions" begin - for t in ("get_field1", "call_normal_fun1", "call_fun1", "call_warm_fun1") + for t in ("get_field1", "call_normal_fun1", + "call_fun1" #, "call_warm_fun1" + ) @testset "regressions in $t" begin med = median(results) #println(t, med)