2017-06-21 51 views
2

我寫了一個小函數來獲取結構體內部字段的字節偏移量,但爲了實現這個功能,我每次遍歷字段直到找到符號。然而,在C中,offsetof()在編譯時只計算一次,因爲偏移在結構內不再改變。我想知道如何儘可能優化它,因爲它是一個非常基本的功能,我需要爲每個框架(3D遊戲引擎)經常調用它。將offsetof()優化到一個常量值?

function offsetof(type_, member::Symbol) 
    for (i, item) in enumerate(fieldnames(type_)) 
    if item == member 
     return fieldoffset(type_, i) 
    end 
    #print(typeof(i)) 
    end 
    # what to do when symbol not in type_? 
    throw("$type_ has no member named $member") 
end 

用法:

type ABC 
    a::Int64 
    b::Int64 
    c::Int64 
end 
offsetof(ABC, :a) # 0 
offsetof(ABC, :b) # 8 
offsetof(ABC, :c) # 16 

望着LLVM代碼,這是一個很大的代碼:

@code_llvm offsetof(Model_s, :fov) 

輸出:

; Function Attrs: uwtable 
define i64 @julia_offsetof_61750(i8**, i8**) #0 !dbg !5 { 
top: 
    %2 = call i8**** @jl_get_ptls_states() #6 
    %3 = alloca [13 x i8**], align 8 
    %.sub = getelementptr inbounds [13 x i8**], [13 x i8**]* %3, i64 0, i64 0 
    %4 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 8 
    %5 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 2 
    %6 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 3 
    %7 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 4 
    %8 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 5 
    %9 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 6 
    %10 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 7 
    %11 = bitcast i8*** %4 to i8* 
    call void @llvm.memset.p0i8.i32(i8* %11, i8 0, i32 40, i32 8, i1 false) 
    %12 = bitcast [13 x i8**]* %3 to i64* 
    %13 = bitcast i8*** %5 to i8* 
    call void @llvm.memset.p0i8.i64(i8* %13, i8 0, i64 40, i32 8, i1 false) 
    store i64 22, i64* %12, align 8 
    %14 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 1 
    %15 = bitcast i8**** %2 to i64* 
    %16 = load i64, i64* %15, align 8 
    %17 = bitcast i8*** %14 to i64* 
    store i64 %16, i64* %17, align 8 
    store i8*** %.sub, i8**** %2, align 8 
    store i8** null, i8*** %10, align 8 
    %18 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 12 
    %19 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 11 
    %20 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 10 
    %21 = getelementptr [13 x i8**], [13 x i8**]* %3, i64 0, i64 9 
    %22 = call i8** @jlsys_fieldnames_43495(i8** inttoptr (i64 375726352 to i8**)) 
    store i8** %22, i8*** %5, align 8 
    %23 = getelementptr inbounds i8*, i8** %22, i64 1 
    %24 = bitcast i8** %23 to i64* 
    %25 = getelementptr i8*, i8** %22, i64 3 
    %26 = bitcast i8** %25 to i64* 
    %27 = bitcast i8** %22 to i8**** 
    %28 = bitcast i8**** %2 to i8* 
    br label %L3 

L3:            ; preds = %cont2, %top 
    %"#temp#1.sroa.4.0" = phi i64 [ 1, %top ], [ %47, %cont2 ] 
    %29 = load i64, i64* %24, align 8 
    %30 = add i64 %29, 1 
    %31 = icmp eq i64 %"#temp#1.sroa.4.0", %30 
    br i1 %31, label %L33, label %if 

if:            ; preds = %L3 
    %32 = add i64 %"#temp#1.sroa.4.0", -1 
    %33 = load i64, i64* %26, align 8 
    %34 = icmp ult i64 %32, %33 
    br i1 %34, label %idxend, label %oob 

L33:            ; preds = %L3 
    store i8** inttoptr (i64 77348880 to i8**), i8*** %4, align 8 
    store i8** inttoptr (i64 77362488 to i8**), i8*** %21, align 8 
    store i8** inttoptr (i64 375726352 to i8**), i8*** %20, align 8 
    store i8** inttoptr (i64 377039808 to i8**), i8*** %19, align 8 
    store i8** %1, i8*** %18, align 8 
    %35 = call i8** @"jsys1_#print_to_string#229_39171"(i8** inttoptr (i64 77362448 to i8**), i8*** %4, i32 5) 
    store i8** %35, i8*** %10, align 8 
    call void @jl_throw(i8** %35) 
    unreachable 

oob:            ; preds = %if 
    %36 = alloca i64, align 8 
    store i64 %"#temp#1.sroa.4.0", i64* %36, align 8 
    call void @jl_bounds_error_ints(i8** %22, i64* nonnull %36, i64 1) 
    unreachable 

idxend:           ; preds = %if 
    %37 = load i8***, i8**** %27, align 8 
    %38 = getelementptr i8**, i8*** %37, i64 %32 
    %39 = load i8**, i8*** %38, align 8 
    %40 = icmp eq i8** %39, null 
    br i1 %40, label %fail, label %cont2 

fail:            ; preds = %idxend 
    call void @jl_throw(i8** inttoptr (i64 84505768 to i8**)) 
    unreachable 

cont2:           ; preds = %idxend 
    store i8** %39, i8*** %6, align 8 
    %41 = call i8** @jl_gc_pool_alloc(i8* %28, i32 1512, i32 32) 
    %42 = getelementptr i8*, i8** %41, i64 -1 
    %43 = bitcast i8** %42 to i8*** 
    store i8** inttoptr (i64 109041296 to i8**), i8*** %43, align 8 
    store i8** %41, i8*** %7, align 8 
    %44 = getelementptr i8*, i8** %41, i64 1 
    %45 = bitcast i8** %44 to i8*** 
    %46 = bitcast i8** %41 to i64* 
    store i64 %"#temp#1.sroa.4.0", i64* %46, align 16 
    store i8** %39, i8*** %45, align 8 
    %47 = add i64 %"#temp#1.sroa.4.0", 1 
    store i8** %39, i8*** %8, align 8 
    store i8** %39, i8*** %9, align 8 
    %48 = icmp eq i8** %39, %1 
    br i1 %48, label %if3, label %L3 

if3:            ; preds = %cont2 
    %sext = shl i64 %"#temp#1.sroa.4.0", 32 
    %49 = ashr exact i64 %sext, 32 
    %50 = icmp eq i64 %49, %"#temp#1.sroa.4.0" 
    br i1 %50, label %pass5, label %fail4 

fail4:           ; preds = %if3 
    call void @jl_throw(i8** inttoptr (i64 77358144 to i8**)) 
    unreachable 

pass5:           ; preds = %if3 
    %51 = trunc i64 %"#temp#1.sroa.4.0" to i32 
    %52 = call i64 inttoptr (i64 1693737504 to i64 (i8**, i32)*)(i8** inttoptr (i64 375726352 to i8**), i32 %51) 
    %53 = load i64, i64* %17, align 8 
    store i64 %53, i64* %15, align 8 
    ret i64 %52 
} 
+0

可能是[生成函數](https://docs.julialang.org/en/latest/manual/metaprogramming/#Generated-functions-1)的一種情況。但我沒有使用它們的經驗。 – phg

+0

我會記憶函數(使用閉包),或者創建一個輔助struct _once_,它包含偏移量,然後我會查詢它。 (或者兩者的組合) –

+1

有一個'Base.fieldindex(ABC,:a)#=> 1'函數。 – Gnimuc

回答

4

這可能是使用 - 情況爲[email protected]表示法。對於這個問題的例子,它會去:

# note the [email protected] notation at beginning of definition: 

[email protected] function offsetof(type_, member::Symbol) 
    for (i, item) in enumerate(fieldnames(type_)) 
    if item == member 
     return fieldoffset(type_, i) 
    end 
    #print(typeof(i)) 
    end 
    # what to do when symbol not in type_? 
    throw("$type_ has no member named $member") 
end 

現在,我們可以:

type ABC 
    a::Int64 
    b::Int64 
    c::Int64 
end 
offsetof(ABC, :a) # 0 

offsetof@code_llvm還長。但是如果我們在函數中使用它,Julia將在編譯時運行offsetof(這是允許的,因爲純函數應該依賴於它們的參數,並且除了返回從它們計算的值之外別無其他)。例如:

julia> f() = offsetof(ABC,:b) 
f (generic function with 1 method) 

julia> f() 
0x0000000000000008 

julia> @code_llvm f() 

define i64 @julia_f_63287() #0 !dbg !5 { 
top: 
    %ptls_i8 = call i8* asm "movq %fs:0, $0;\0Aaddq $$-10928, $0", "=r,~{dirflag},~{fpsr},~{flags}"() #1 
    ret i64 8 
} 

注意f()包括剛剛ret i64 8返回0x8offsetof(ABC,:b)值。

這裏是描述@pure符號的鏈接:https://github.com/JuliaLang/julia/issues/414
(參見:https://github.com/JuliaLang/julia/issues/14324

小心的詞是必要:純符號可能是在磁通和可版本之間變化。它工作在0.6。在未來,我們可以使用一個不斷傳播的智能編譯器更容易地獲得這種優化。

1

這是更好地使用Base.fieldindex獲得指數,而不是通過字段中手動迭代:

offsetof(type_, member::Symbol) = fieldoffset(type_, Base.fieldindex(type_, member)) 

幫助> Base.fieldindex
字段索引(T,姓名::符號,ERR:BOOL = true)

獲取已命名字段的索引,如果該字段不存在(當err == true時)或返回0(當err == false時),則會引發錯誤。

julia> struct Foo 
     x::Int64 
     y::String 
    end 
julia> Base.fieldindex(Foo, :z) 
ERROR: type Foo has no field z 
Stacktrace:  
    [1] fieldindex at ./reflection.jl:319 [inlined](repeats 2 times) 
julia> Base.fieldindex(Foo, :z, false) 
0 

的LLVM-IR輸出仍然是一個有點長(不下來,以一個簡單的常量),但我想這已經足夠簡單的對原始版本:

define i64 @julia_offsetof_61272(i8**, i8**) #0 !dbg !5 { 
top: 
    %2 = call i32 inttoptr (i64 4332697664 to i32 (i8**, i8**, i32)*)(i8** inttoptr (i64 4713207440 to i8**), i8** %1, i32 1) 
    %3 = sext i32 %2 to i64 
    %4 = add nsw i64 %3, 1 
    %sext = shl i64 %4, 32 
    %5 = ashr exact i64 %sext, 32 
    %6 = icmp eq i64 %5, %4 
    br i1 %6, label %pass2, label %fail1 

fail1:           ; preds = %top 
    call void @jl_throw(i8** inttoptr (i64 4412470696 to i8**)) 
    unreachable 

pass2:           ; preds = %top 
    %7 = trunc i64 %4 to i32 
    %8 = call i64 inttoptr (i64 4332698112 to i64 (i8**, i32)*)(i8** inttoptr (i64 4713207440 to i8**), i32 %7) 
    ret i64 %8 
} 
0

非常感謝兩個答案,我想他們可以很容易地結合在一起,使最快,最緊湊的版本:

type ABC 
    a::Int64 
    b::Int64 
    c::Int64 
end 

[email protected] offsetof(type_, member::Symbol) = fieldoffset(type_, Base.fieldindex(type_, member)) 

code() = offsetof(ABC, :b) 

@code_llvm code() 

它打印:

; Function Attrs: uwtable 
define i64 @julia_code_61654() #0 !dbg !5 { 
top: 
    ret i64 8 
}