diff --git a/package/src/ExpressionExecutorCuda.jl b/package/src/ExpressionExecutorCuda.jl index 5c6a32b..35ac877 100644 --- a/package/src/ExpressionExecutorCuda.jl +++ b/package/src/ExpressionExecutorCuda.jl @@ -1,6 +1,6 @@ module ExpressionExecutorCuda -include("Interpreter.jl") include("ExpressionProcessing.jl") +include("Interpreter.jl") export interpret_gpu export evaluate_gpu @@ -33,28 +33,6 @@ end function evaluate_gpu(exprs::Vector{Expr}, X::Matrix{Float32}, p::Vector{Vector{Float32}})::Matrix{Float32} end -function test() - Interpreter.CudaTest() -end - -"Performs pre processing steps to the expressions. - - It replaces every variable with the according value stored in X and p. - - It transforms the expressions into postfix form and returns them. -" -function preprocess_expressions!(exprs::Vector{Expr}, X::Matrix{Float64}, p::Vector{Vector{Float64}})::Array{String} - symtable = ExpressionProcessing.construct_symtable(exprs, X, p) - postfixExpressions = Array{String,1}() - - # Test if multi threading provides a speedup and if it does, roughly determin the size at which it is beneficial. - for i in eachindex(exprs) - expr = deepcopy(exprs[i]) - ExpressionProcessing.replace_variables!(exprs[i], symtable, expr) - push!(postfixExpressions, ExpressionProcessing.expr_to_postfix(exprs[i])) - end - - return postfixExpressions -end - end diff --git a/package/src/ExpressionProcessing.jl b/package/src/ExpressionProcessing.jl index 5ec6a8f..28a0f45 100644 --- a/package/src/ExpressionProcessing.jl +++ b/package/src/ExpressionProcessing.jl @@ -1,17 +1,22 @@ module ExpressionProcessing -export construct_symtable -export replace_variables! export expr_to_postfix -export SymbolTable64 export PostfixType export Operator, Add, Subtract, Multiply, Divide, Power, Abs, Log, Exp, Sqrt +export ElementType, FLOAT64, OPERATOR, INT64 +export ExpressionElement -@enum Operator Add=1 Subtract=2 Multiply=3 Divide=4 Power=5 Abs=6 Log=7 Exp=8 Sqrt=9 -const SymbolTable64 = Dict{Tuple{Expr, Symbol},Float64} -const PostfixType = Vector{Union{Float64,Operator,Symbol}} +@enum Operator::Int64 Add=1 Subtract=2 Multiply=3 Divide=4 Power=5 Abs=6 Log=7 Exp=8 Sqrt=9 +@enum ElementType FLOAT64=1 OPERATOR=2 INT64=3 -# Maybe switch from Array{String} to Array{Union{Float64, Symbol}}? +struct ExpressionElement + Type::ElementType + value::Int64 # Reinterpret the stored value to type "ElementType" when using it +end + +const PostfixType = Vector{ExpressionElement} + +"Converts a julia expression to its postfix notation" function expr_to_postfix(expr::Expr)::PostfixType postfix = PostfixType() operator = get_operator(expr.args[1]) @@ -20,20 +25,81 @@ function expr_to_postfix(expr::Expr)::PostfixType for j in 2:length(expr.args) arg = expr.args[j] if typeof(arg) === Expr + # exprElement = convert_to_ExpressionElement() append!(postfix, expr_to_postfix(arg)) elseif typeof(arg) === Symbol # variables/parameters - push!(postfix, arg) + exprElement = convert_to_ExpressionElement(convert_var_to_int(arg)) + push!(postfix, exprElement) else - push!(postfix, convert(Float64, arg)) + exprElement = convert_to_ExpressionElement(convert(Float64, arg)) + push!(postfix, exprElement) end if length(postfix) >= 2 - push!(postfix, operator) + exprElement = convert_to_ExpressionElement(operator) + push!(postfix, exprElement) end end return postfix end +function get_operator(op::Symbol)::Operator + if op == :+ + return Add + elseif op == :- + return Subtract + elseif op == :* + return Multiply + elseif op == :/ + return Divide + elseif op == :^ + return Power + elseif op == :abs + return Abs + elseif op == :log + return Log + elseif op == :exp + return Exp + elseif op == :sqrt + return Sqrt + end +end + +"Extracts the number from a variable/parameter and returns it. If the symbol is a parameter ```pn```, the resulting value will be negativ. + +```x0 and p0``` are not allowed." +function convert_var_to_int(var::Symbol)::Int + varStr = String(var) + number = parse(Int32, SubString(varStr, 2)) + + if varStr[1] == 'p' + number = -number + end + + return number +end + +function convert_to_ExpressionElement(element)::ExpressionElement + value = reinterpret(Int64, element) + if element isa Float64 + return ExpressionElement(FLOAT64, value) + elseif element isa Int64 + return ExpressionElement(INT64, value) + elseif element isa Operator + return ExpressionElement(OPERATOR, value) + else + error("Element was of type '$(typeof(element))', which is not supported.") + end +end + + + +# +# Everything below is currently not needed. Left here for potential future use +# + +const SymbolTable64 = Dict{Tuple{Expr, Symbol},Float64} + "Replaces all the variables and parameters of the given expression with their corresponding value stored in the symtable # Arguments - `symtable::SymbolTable64`: Contains the values of all variables for each expression @@ -78,26 +144,4 @@ function fill_symtable!(expr::Expr, symtable::SymbolTable64, values::Vector{Floa end end -function get_operator(op::Symbol)::Operator - if op == :+ - return Add - elseif op == :- - return Subtract - elseif op == :* - return Multiply - elseif op == :/ - return Divide - elseif op == :^ - return Power - elseif op == :abs - return Abs - elseif op == :log - return Log - elseif op == :exp - return Exp - elseif op == :sqrt - return Sqrt - end -end - -end +end \ No newline at end of file diff --git a/package/src/Interpreter.jl b/package/src/Interpreter.jl index bb70814..7f679ea 100644 --- a/package/src/Interpreter.jl +++ b/package/src/Interpreter.jl @@ -1,36 +1,8 @@ module Interpreter using CUDA -include("ExpressionProcessing.jl") -using .ExpressionProcessing: PostfixType, Add, Subtract, Operator +using ..ExpressionProcessing -export Interpret -export CudaTest - -function CudaTest() - N = 2^20 - x = CUDA.fill(1.0f0, N) - y = CUDA.fill(2.0f0, N) - - kernelAdd = @cuda launch=false InterpretExplicit!(ExpressionProcessing.Add, x, y) - # kernelAdd = @cuda launch=false InterpretExplicit!(Add, x, y, reference) - config = launch_configuration(kernelAdd.fun) - threads = min(length(y), config.threads) - blocks = cld(length(y), threads) - - kernelAdd(Add, x, y; threads, blocks) - # println(y[1]) - # @test all(Array(y) .== 3.0f0) - - kernelSubtract = @cuda launch=false InterpretExplicit!(ExpressionProcessing.Subtract, x, y) - configSub = launch_configuration(kernelSubtract.fun) - threadsSub = min(length(y), configSub.threads) - blocksSub = cld(length(y), threadsSub) - CUDA.fill!(y, 2.0f0) - - # kernelSubtract(Subtract, x, y; threadsSub, blocksSub) - # @test all(Array(y) .== -1.0f0) - # println(y[1]) -end +export interpret "Interprets the given expressions with the values provided. # Arguments @@ -38,19 +10,49 @@ end - variables::Matrix{Float64} : The variables to use. Each column is mapped to the variables x1..xn - parameters::Vector{Vector{Float64}} : The parameters to use. Each Vector contains the values for the parameters p1..pn. The number of parameters can be different for every expression " -function Interpret(expressions::Vector{ExpressionProcessing.PostfixType}, variables::Matrix{Float64}, parameters::Vector{Vector{Float64}}) +function interpret(expressions::Vector{ExpressionProcessing.PostfixType}, variables::Matrix{Float64}, parameters::Vector{Vector{Float64}}) # TODO: - # create CUDA array and fill it with the expressions, variables and parameters - # calculate needed number of threads, probably based off of the number of expressions, so I can ensure each warp takes the same execution path - # Start the kernel - cudaExprs = Vector{CuArray{ExpressionProcessing.PostfixType}}(undef, length(expressions)) + # create CUDA array and fill it with the variables and parameters + # create CUDA array for calculation results + variableRows = size(variables, 1) + cudaVars = CuArray(variables) + + paramRows = get_max_parameter_rows(parameters) + p1aramCols = length(parameters) + cudaParams = CuArray{Float64}(undef, p1aramCols, paramRows) # length(parameters) == number of expressions + # TODO: Fill cudaParams + + # TODO: Move CuArray(expression[i]) outside the loop for a more efficient transfer to GPU but leave kernel signature as is for i in eachindex(expressions) - push!(cudaExprs, CuArray(expressions[i])) + cudaExpr = CuArray(expressions[i]) + kernel = @cuda launch=false interpret_expression(cudaExpr, cudaVars, cudaParams, i) + config = launch_configuration(kernel.fun) + threads = min(variableRows, config.threads) + blocks = cld(variableRows, threads) + + kernel(cudaExpr, cudaVars, cudaParams, i; threads, blocks) end - # cudaExprs = CuArray(copy(expressions)) +end + +function interpret_expression(expression, variables, parameters, exprIndex::Int) + #TODO Implement interpreter +end + +"Retrieves the number of entries for the largest inner vector" +function get_max_parameter_rows(params::Vector{Vector{T}})::Int where T + maxLength = 0 + for i in eachindex(params) + if length(params) > maxLength + maxLength = length(params) + end + end + + return maxLength end + +@deprecate InterpretExplicit!(op::Operator, x, y) interpret_expression(expression, variables, parameters, exprIndex::Int) # Kernel function InterpretExplicit!(op::Operator, x, y) index = (blockIdx().x - 1) * blockDim().x + threadIdx().x diff --git a/package/test/ExpressionProcessingTests.jl b/package/test/ExpressionProcessingTests.jl index 5facc36..768dfaf 100644 --- a/package/test/ExpressionProcessingTests.jl +++ b/package/test/ExpressionProcessingTests.jl @@ -11,28 +11,21 @@ variables[1,2] = 3 parameters[1] = Vector{Float64}(undef, 1) parameters[1][1] = 5 -@testset "Test symtable construction" begin - symtable = construct_symtable(expressions, variables, parameters) - @test haskey(symtable, (expressions[1], :x1)) - @test haskey(symtable, (expressions[1], :x2)) - @test haskey(symtable, (expressions[1], :p1)) - - @test symtable[(expressions[1], :x1)] ≈ 2.0 atol=0.01 - @test symtable[(expressions[1], :x2)] ≈ 3.0 atol=0.01 - @test symtable[(expressions[1], :p1)] ≈ 5.0 atol=0.01 -end - -@testset "Test variable replacement in expression" begin - symtable = construct_symtable(expressions, variables, parameters) - exprCopy = deepcopy(expressions[1]) - replace_variables!(exprCopy, symtable, expressions[1]) - - @test eval(exprCopy) == 10 # If it can be evaluated, it means all variables have been replaced, as there are no globally defined variables +@testset "Test conversion expression element" begin + reference1 = ExpressionElement(FLOAT64, reinterpret(Int64, 1.0)) + reference2 = ExpressionElement(INT64, reinterpret(Int64, 1)) + reference3 = ExpressionElement(OPERATOR, reinterpret(Int64, Add)) + + @test isequal(reference1, ExpressionProcessing.convert_to_ExpressionElement(1.0)) + @test isequal(reference2, ExpressionProcessing.convert_to_ExpressionElement(1)) + @test isequal(reference3, ExpressionProcessing.convert_to_ExpressionElement(Add)) end @testset "Test conversion to postfix" begin reference = PostfixType() - append!(reference, [:x1, 1.0, :x2, Multiply, Add, :p1, Add]) + + append!(reference, [ExpressionProcessing.convert_to_ExpressionElement(1), ExpressionProcessing.convert_to_ExpressionElement(1.0), ExpressionProcessing.convert_to_ExpressionElement(2), ExpressionProcessing.convert_to_ExpressionElement(Multiply), + ExpressionProcessing.convert_to_ExpressionElement(Add), ExpressionProcessing.convert_to_ExpressionElement(-1), ExpressionProcessing.convert_to_ExpressionElement(Add)]) postfix = expr_to_postfix(expressions[1]) @test isequal(reference, postfix)