From e27149f1a6e20c091889025c6e36cb3c3e23ec9b Mon Sep 17 00:00:00 2001 From: John Tinnerholm Date: Wed, 10 Jan 2024 16:50:29 +0100 Subject: [PATCH] Various performance improvements (#61) * Minor performance improvements to MetaModelica.jl * Fixing formatting of dangerous * Updates to MetaModelica.jl * Updated versioning * Changed Jenkinsfile to use 1.10 bookworm from 1.1 * Added test as an explicit dependency --- .CI/Jenkinsfile | 2 +- Project.toml | 6 +- src/MetaModelica.jl | 2 +- src/dangerous.jl | 153 +++++++++++++++++++++++++++++++++++++------ src/matchcontinue.jl | 4 +- src/metaRuntime.jl | 58 +++++++++++----- src/union.jl | 32 ++++++--- src/utilityMacros.jl | 26 +++++--- test/runtests.jl | 4 -- 9 files changed, 226 insertions(+), 61 deletions(-) diff --git a/.CI/Jenkinsfile b/.CI/Jenkinsfile index 9e4b83f..9d8bd41 100644 --- a/.CI/Jenkinsfile +++ b/.CI/Jenkinsfile @@ -3,7 +3,7 @@ pipeline { docker { // Large image with full OpenModelica build dependencies; lacks omc and OMPython label 'linux' - image 'julia:1.1-buster' + image 'julia:1.10.0-bookworm' alwaysPull true args '--privileged' } diff --git a/Project.toml b/Project.toml index 7d4284f..c8c4242 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,16 @@ name = "MetaModelica" uuid = "9d7f2a79-07b5-5542-8b19-c0100dda6b06" authors = ["Martin Sjölund ", "John Tinnerholm "] -version = "0.0.3" +version = "0.0.4" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" +FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" ImmutableList = "4a558cac-c1ed-11e9-20da-3584bcd8709a" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -julia = "1.1" +julia = "1.7" diff --git a/src/MetaModelica.jl b/src/MetaModelica.jl index e65feb9..6857a2e 100644 --- a/src/MetaModelica.jl +++ b/src/MetaModelica.jl @@ -26,7 +26,7 @@ export @match, @matchcontinue, MatchFailure, ModelicaReal, ModelicaInteger export @Uniontype, @Record, @UniontypeDecl, @ExtendedFunction, @ExtendedAnonFunction export List, list, Nil, nil, Cons, cons, =>, Option, SOME, NONE, SourceInfo, SOURCEINFO export @do_threaded_for, <|, @shouldFail, sourceInfo, _cons, @importDBG -export @assign +export @assign, @Mutable_Uniontype, @closure include("exportmetaRuntime.jl") include("dangerous.jl") diff --git a/src/dangerous.jl b/src/dangerous.jl index abb673f..b447249 100644 --- a/src/dangerous.jl +++ b/src/dangerous.jl @@ -9,63 +9,178 @@ import ExportAll using ..MetaModelica """ O(1) """ -function arrayGetNoBoundsChecking(arr::Array, index::ModelicaInteger) +function arrayGetNoBoundsChecking(arr::Vector{A}, index::ModelicaInteger) where {A} @inbounds arr[index] end """ O(1) """ -function arrayUpdateNoBoundsChecking(arr::Array{T}, index::ModelicaInteger, newValue::T) where {T} +function arrayUpdateNoBoundsChecking(arr::Vector{A}, + index::ModelicaInteger, + newValue::A) where {A} local newArray = arr - if index < 0 - println("arrayUpdateNoBoundsChecking: index < 0!") - end @inbounds newArray[index] = newValue - newArray + return newArray end """ Creates a new array where the elements are *not* initialized!. Any attempt to access an uninitialized elements may cause segmentation faults if you're lucky, and pretty much anything else if you're not. Do not use unless you will immediately fill the whole array with data. The dummy variable is used to fix -the type of the array. """ -function arrayCreateNoInit(size::ModelicaInteger, dummy::T) where {T} - local arr = fill(dummy, size) +the type of the array. +""" +function arrayCreateNoInit(size::ModelicaInteger, dummy::A)::Array{A} where {A} + local arr::Array{A} = fill(dummy, size) arr end """ O(1) """ -function stringGetNoBoundsChecking(str::String, index::ModelicaInteger)::ModelicaInteger +function stringGetNoBoundsChecking(str::String, index::ModelicaInteger) local ch::ModelicaInteger - if index < 0 - println("stringGetNoBoundsChecking: index < 0!") - end ch = @inbounds str[index] end """ Not possible unless we write a C list impl for Julia """ -function listReverseInPlace(inList::List{T}) where {T} +function listReverseInPlace(inList::List{T})::List{T} where {T} MetaModelica.listReverse(inList) end +function listReverseInPlace2(inList::Nil) + return inList#MetaModelica.listReverse(inList) +end + +""" + Unsafe implementation of list reverse in place. + Instead of creating new cons cells we swap pointers... +""" +function listReverseInPlace2(lst::Cons{T}) where {T} + local prev = nil + #= Declare an unsafe pointer to the list =# + local oldCdrPtr::Ptr{List{T}} + GC.@preserve while (!(lst isa Nil)) + println("prev at the iteration:") + println(prev) + println("lst at the iteration:") + println(lst) + println("before oldCdr = $(lst.tail)") + oldCdr = deepcopy(lst.tail) + println("Before listSetRest($lst, $prev)") + listSetRest(lst, prev) + println("Before prev = $lst") + prev = lst + println("Before lst = $(oldCdr) //oldCdr") + lst = oldCdr + end + println("After loop") + return prev +end + + +# """ +# O(1). A destructive operation changing the \"first\" part of a cons-cell. +# TODO: Not implemented +# """ +# function listSetFirst(inConsCell::Cons{A}, inNewContent::A) where {A} #= A non-empty list =# +# firstPtr::Ptr{A} = unsafe_getListAsPtr(inConsCell) +# #local newHead = Cons{T}(inNewContent, inConsCell.tail) +# # unsafe_store!(firstPtr, inNewContent) +# end + +""" O(1). A destructive operation changing the rest part of a cons-cell """ +#= NOTE: Make sure you do NOT create cycles as infinite lists are not handled well in the compiler. =# +function listSetRest(inConsCell::Cons{A}, inNewRest::Cons{A}) where {A} #= A non-empty list =# + newTailPtr::Ptr{Cons{A}} = unsafe_getListAsPtr(inNewRest) + inConsCellTailPtr::Ptr{Cons{A}} = unsafe_getListTailAsPtr(inConsCell) + inConsCellTailPtr2::Ptr{Cons{A}} = unsafe_getListAsPtr(inConsCell) + GC.@preserve(unsafe_store!(inConsCellTailPtr, unsafe_load(newTailPtr))) + return inConsCell +end + +""" + We create one cons cell when the tail we are setting is a nil... +""" +function listSetRest(inConsCell::Cons{A}, inNewRest::Nil) where {A} #= A non-empty list =# + local lstPtr::Ptr{Cons{A}} = unsafe_getListAsPtr(inConsCell) + local val = inConsCell.head + GC.@preserve unsafe_store!(lstPtr, Cons{A}(inConsCell.head, inNewRest)) + return inConsCell +end + """ O(1). A destructive operation changing the \"first\" part of a cons-cell. """ function listSetFirst(inConsCell::Cons{A}, inNewContent::A) where {A} #= A non-empty list =# @assign inConsCell.head = inNewConent end -""" -O(1). A destructive operation changing the rest part of a cons-cell -NOTE: Make sure you do NOT create cycles as infinite lists are not handled well in the compiler. +""" +O(1). A destructive operation changing the rest part of a cons-cell +NOTE: Make sure you do NOT create cycles as infinite lists are not handled well in the compiler. """ function listSetRest(inConsCell::Cons{T}, inNewRest::List{T}) where {T} #= A non-empty list =# @assign inConsCell.tail = inNewRest end + """ O(n) """ -function listArrayLiteral(lst::List{T}) where {T} - local arr = listArray(lst) +function listArrayLiteral(lst::List{A})::Array{A} where {A} + local arr::Array{A} = listArray(lst) arr end +""" +``` +listGetFirstAsPtr(lst::Cons{T})::Ptr{T} +``` + +Dangerous function. +Gets the first element of the list as a pointer of type T. +Unless it is nil then we get a NULL pointer +""" +function unsafe_getListHeadAsPtr(lst::Cons{T}) where{T} + convert(Ptr{T}, unsafe_pointer_from_objref(lst.head)) +end + +""" +``` listGetFirstAsPtr(nil)::Ptr{Nothing}``` +Returns a null pointer +""" +function unsafe_getListHeadAsPtr(lst::Nil) + unsafe_pointer_from_objref(nil) +end + +""" + Fetches the pointer to the tail of the list +``` +unsafe_listGetTailAsPtr{lst::List{T}}::Ptr{Cons{T}} +``` +""" +function unsafe_getListTailAsPtr(lst::List{T}) where {T} + if lst.tail === nil + return unsafe_pointer_from_objref(nil) + else + convert(Ptr{Cons{T}}, unsafe_pointer_from_objref(lst.tail)) + end +end + +""" +Unsafley get a pointer to a list. +""" +function unsafe_getListAsPtr(lst::List{T}) where {T} + if lst === nil + ptrToNil::Ptr{Nil{Any}} = unsafe_pointer_from_objref(nil) + return ptrToNil + else + convert(Ptr{Cons{T}}, unsafe_pointer_from_objref(lst)) + end +end + +""" + Unsafe function to get pointers from immutable struct. + Use with !care! +""" +function unsafe_pointer_from_objref(@nospecialize(x)) + ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x) +end + + ExportAll.@exportAll() end #=End dangerous =# diff --git a/src/matchcontinue.jl b/src/matchcontinue.jl index 15e301b..ba940e4 100644 --- a/src/matchcontinue.jl +++ b/src/matchcontinue.jl @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. - The code is based on https://github.com/RelationalAI-oss/Rematch.jl with - changes to allow keyword argument matching on structs along with + The code is originally based on https://github.com/RelationalAI-oss/Rematch.jl with + changes to allow keyword argument matching on structs along with matching on the immutable list construct accompanying MetaModelica + some other improvements and bug fixes. It also provides @matchcontinue macro (try the next case when any exception is thrown). """ diff --git a/src/metaRuntime.jl b/src/metaRuntime.jl index 359e780..23a373d 100644 --- a/src/metaRuntime.jl +++ b/src/metaRuntime.jl @@ -369,18 +369,20 @@ end Example: stringDelimitList({\"x\",\"y\",\"z\"}, \", \") => \"x, y, z\" """ function stringDelimitList(strs::List{String}, delimiter::String)::String - local str::String = "" - for n in strs - if isempty(str) - str = n + buffer = IOBuffer() + for (i,n) in enumerate(strs) + if i == 1 + print(buffer, n) else - str = str + delimiter + n + print(buffer, delimiter) + print(buffer, n) + #str = str + delimiter + n end end - str + return String(take!(buffer))#str end -function stringDelimitList(strs::List{Any}, delimiter::String)::String +function stringDelimitList(strs::List, delimiter::String)::String local str::String = "" for n in strs if isempty(str) @@ -528,11 +530,15 @@ end """ O(n) """ function listArray(lst::Cons{T}) where {T} - local arr::Vector{T} = T[] - for i in lst - push!(arr, i) + local N = length(lst) + local arr::Vector{T} = Vector{T}(undef, N) + i = 1 + while lst !== nil + arr[i] = lst.head + i += 1 + lst = lst.tail end - arr + return arr end """ O(1) """ @@ -540,6 +546,26 @@ function listArray(lst::Nil) [] end +""" +O(n) + +Same as listArray but with a dummy argument to specify the type. +""" +function listArray(lst::Cons{T}, ty) where {T} + local arr = Vector{ty}(undef, length(lst)) + for i in lst + arr[i] + end + return arr +end + +""" +Same as listArray but with a dummy argument to specify the type. +""" +function listArray(lst::Nil, ty) + ty[] +end + """ O(1) """ function arrayUpdate(arr::Array{A}, index::ModelicaInteger, newValue::B)::Array{A} where {A,B} @@ -558,9 +584,8 @@ end Note that this operation is *not* destructive, i.e. a new array is created. """ function arrayAppend(arr1::Array{A}, arr2::Array{A})::Array{A} where {A} local arr::Array{A} - - #= Defined in the runtime =# - arr + @error "Defined in the runtime" + fail() end """ Returns the string representation of any value. @@ -605,7 +630,7 @@ end This is a global mutable value and should be used sparingly. You are recommended not to use "missing" the runtime system treats this values as uninitialized and fail getGlobalRoot later on. """ -global globalRoots = Array{Any,1}(missing, 1024) +const global globalRoots::Vector{Any} = Vector{Any}(missing, 1024) function setGlobalRoot(index::ModelicaInteger, value::T) where {T} if index > 1023 || index < 0 @@ -735,7 +760,8 @@ function referenceDebugString(functionSymbol::A)::String where {A} name end -""" TODO: I am far from sure that this will fly.. in Julia. The code generated from the transpiler is correct however""" +""" TODO: I am far from sure that this will fly.. in Julia. +The code generated from the transpiler is correct however""" function isPresent(ident::T)::Bool where {T} local b::Bool b = true diff --git a/src/union.jl b/src/union.jl index 9648707..37c02e5 100644 --- a/src/union.jl +++ b/src/union.jl @@ -74,15 +74,23 @@ end function replaceLineNum(a::Any, lines::LineNumberNode) end -function makeUniontypes(name, records, lineNode::LineNumberNode) +function makeUniontypes(name, records, lineNode::LineNumberNode; mutable = false) recordsArray1 = Array.(records) recordsArray2 = recordsArray1[1] constructedRecords = [] - for r in recordsArray2 + for r in recordsArray2 structName = r[1] - recordNode = quote - struct $(structName) <: $name - $(r[2]) + recordNode = if ! mutable + quote + struct $(structName) <: $name + $(r[2]) + end + end + else + quote + mutable struct $(structName) <: $name + $(r[2]) + end end end replaceLineNum(recordNode, isa(r[3], Nothing) ? lineNode : r[3]) @@ -99,13 +107,21 @@ function makeUniontypes(name, records, lineNode::LineNumberNode) return res end -#= Creates a uniontype consisting of 0...N records =# +""" Creates a uniontype consisting of 0...N records """ macro Uniontype(name, records...) recordCollection = [makeRecord(r) for r in records] esc(makeUniontypes(name, recordCollection, __source__)) end -#= Creates a record belonging to a Uniontype =# +""" + Creates a mutable uniontype constisting of 0...N records +""" +macro Mutable_Uniontype(name, records...) + recordCollection = [makeRecord(r) for r in records] + esc(makeUniontypes(name, recordCollection, __source__; mutable = true)) +end + +""" Creates a record belonging to a Uniontype """ macro Record(name, fields...) makeTuple(name, fields) end @@ -117,6 +133,6 @@ macro UniontypeDecl(uDecl) end) end -export @Uniontype, @Record, @UniontypeDecl +export @Uniontype, @Record, @UniontypeDecl, @Mutable_Uniontype end diff --git a/src/utilityMacros.jl b/src/utilityMacros.jl index fa557b8..f259235 100644 --- a/src/utilityMacros.jl +++ b/src/utilityMacros.jl @@ -1,23 +1,25 @@ import Setfield +import FastClosures """ Helper function for the assignmacro, see @assign We have one case where we assign a immutable structure to an immutable structure or something to a primitive variable. If it is not a primitive we assign to a subcomponent of that structure. We then clone the structure with that particular field changed. """ function assignFunc(expr) - res = if @capture(expr, lhs_._ = rhs_) - if !isprimitivetype(typeof(lhs)) - Setfield.setmacro(identity, expr, overwrite=true) + res = + if @capture(expr, lhs_._ = rhs_) + if !isprimitivetype(typeof(lhs)) + Setfield.setmacro(identity, expr, overwrite=true) + else + quote + $(esc(expr)) + end + end else quote $(esc(expr)) end end - else - quote - $(esc(expr)) - end - end return res end @@ -33,3 +35,11 @@ E.g.: macro assign(expr) assignFunc(expr) end + +""" + Wraps the @closure macro of FastClosures. +See the FastClosure package for more information. +""" +macro closure(expr) + esc(FastClosures.wrap_closure(__module__, expr)) +end diff --git a/test/runtests.jl b/test/runtests.jl index 4843e1d..76e8414 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,3 @@ -module TestMetaModelica - using MetaModelica using Test @@ -41,5 +39,3 @@ using Test end end #= End MetaModelica testset =# - -end #= End of TestMetaModelica =#