shaderlabでレイマーチングのシェーダーを書き,マーチングループの部分のアセンブリコードを確認して不満に思う点があった. 本記事ではマーチングその不満に思う点を解消する手法について述べる.
扱うシェーダーコード
この記事では下記のシェーダーコードを題材として取り上げる.
レイマーチングによる単純なスフィアトレーシングのshaderlabのコードであり,ForwardBaseパスのみサポートしている.
ライティングは単純なLambert反射のみの計算であり,鏡面反射や環境光の計算は除外している.
出力アセンブリコードの増加を防ぐため,#pragma multi_compile_fog
と #pragma multi_compile_fwdbase
は宣言していない.
Shader "koturn/RayMarching/LoopBreak" { Properties { [IntRange] _MaxLoop ("Maximum loop count", Range(8, 1024)) = 128 _MinRayLength ("Minimum length of the ray", Float) = 0.01 _MaxRayLength ("Maximum length of the ray", Float) = 1000.0 _MarchingFactor ("Marching Factor", Range(0.5, 1.0)) = 1.0 _Color ("Color of the objects", Color) = (1.0, 1.0, 1.0, 1.0) [KeywordEnum(Post If Break, Post Flatten If Break, Pre If Break, Pre Flatten If Break, Use Loop Continuous, Use Index Update, Post Update Index)] _BreakMethod ("Break method of the marching loop", Int) = 0 } SubShader { Tags { "Queue" = "AlphaTest" "RenderType" = "Transparent" "DisableBatching" = "True" "IgnoreProjector" = "True" "VRCFallback" = "Hidden" } Cull Front CGINCLUDE #pragma target 3.0 // keywords: // FOG_LINEAR // FOG_EXP // FOG_EXP2 // #pragma multi_compile_fog #pragma multi_compile_local_fragment _BREAKMETHOD_POST_IF_BREAK _BREAKMETHOD_POST_FLATTEN_IF_BREAK _BREAKMETHOD_PRE_IF_BREAK _BREAKMETHOD_PRE_FLATTEN_IF_BREAK _BREAKMETHOD_USE_LOOP_CONTINUOUS _BREAKMETHOD_USE_INDEX_UPDATE _BREAKMETHOD_POST_UPDATE_INDEX #include "UnityCG.cginc" #include "UnityStandardUtils.cginc" #include "AutoLight.cginc" /*! * @brief Input of the vertex shader, vert(). */ struct appdata { //! Local position of the vertex. float4 vertex : POSITION; //! Lightmap coordinate. float2 texcoord1 : TEXCOORD1; }; /*! * @brief Output of the vertex shader, frag() * and input of fragment shader. */ struct v2f { //! Clip space position of the vertex. float4 pos : SV_POSITION; //! Ray origin in object space (Camera position in object space). nointerpolation float3 localRayOrigin : TEXCOORD0; //! Unnormalized ray direction in object space. float3 localRayDirVector : TEXCOORD1; //! Lighting and shadowing parameters. UNITY_LIGHTING_COORDS(2, 3) }; /*! * @brief Output of fragment shader. */ struct fout { //! Output color of the pixel. half4 color : SV_Target; //! Depth of the pixel. float depth : SV_Depth; }; float map(float3 p); float sdSphere(float3 p, float r); half4 calcLighting(half4 color, float3 worldPos, float3 worldNormal, half atten); float3 getNormal(float3 p); float getDepth(float4 projPos); #ifndef UNITY_LIGHTING_COMMON_INCLUDED fixed4 _LightColor0; #endif // UNITY_LIGHTING_COMMON_INCLUDED //! Color of the objects. uniform half4 _Color; //! Maximum loop count. uniform int _MaxLoop; //! Minimum length of the ray. uniform float _MinRayLength; //! Maximum length of the ray. uniform float _MaxRayLength; //! Marching Factor. uniform float _MarchingFactor; /*! * @brief Vertex shader function for ForwardBase and ForwardAdd Pass. * @param [in] v Input data * @return Output for fragment shader (v2f). */ v2f vert(appdata v) { v2f o; UNITY_INITIALIZE_OUTPUT(v2f, o); o.pos = UnityObjectToClipPos(v.vertex); o.localRayOrigin = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0)).xyz; o.localRayDirVector = v.vertex - o.localRayOrigin; UNITY_TRANSFER_LIGHTING(o, v.texcoord1); return o; } /*! * @brief Fragment shader function. * @param [in] fi Input data from vertex shader. * @return Output of each texels (fout). */ fout frag(v2f fi) { const float3 ro = fi.localRayOrigin; const float3 rd = normalize(fi.localRayDirVector); float t = 0.0; #if defined(_BREAKMETHOD_POST_IF_BREAK) for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; if (d < _MinRayLength || t > _MaxRayLength) { break; } } #elif defined(_BREAKMETHOD_POST_FLATTEN_IF_BREAK) for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { break; } } #elif defined(_BREAKMETHOD_PRE_IF_BREAK) float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i++) { if (d < _MinRayLength || t > _MaxRayLength) { break; } d = map(ro + rd * t); t += d * _MarchingFactor; } #elif defined(_BREAKMETHOD_PRE_FLATTEN_IF_BREAK) float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i++) { UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { break; } d = map(ro + rd * t); t += d * _MarchingFactor; } #elif defined(_BREAKMETHOD_USE_LOOP_CONTINUOUS) float d = _MaxRayLength; for (int i = 0; i < _MaxLoop && d >= _MinRayLength && t <= _MaxRayLength; i++) { d = map(ro + rd * t); t += d * _MarchingFactor; } #elif defined(_BREAKMETHOD_USE_INDEX_UPDATE) float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i = (d < _MinRayLength || t > _MaxRayLength) ? 0x7fffffff : i + 1) { d = map(ro + rd * t); t += d * _MarchingFactor; } #else // defined(_BREAKMETHOD_POST_UPDATE_INDEX) for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { i = 0x7ffffffe; } } #endif clip(_MaxRayLength - t); const float3 localFinalPos = ro + rd * t; const float3 worldFinalPos = mul(unity_ObjectToWorld, float4(localFinalPos, 1.0).xyz); UNITY_LIGHT_ATTENUATION(atten, fi, worldFinalPos); half4 color = calcLighting( _Color, worldFinalPos, UnityObjectToWorldNormal(getNormal(localFinalPos)), atten); const float4 projPos = UnityWorldToClipPos(worldFinalPos); UNITY_APPLY_FOG(projPos.z, color); fout fo; UNITY_INITIALIZE_OUTPUT(fout, fo); fo.color = color; fo.depth = getDepth(projPos); return fo; } /*! * @brief SDF (Signed Distance Function) of objects. * @param [in] p Position of the tip of the ray. * @return Signed Distance to the nearest object. */ float map(float3 p) { return sdSphere(p, 0.5); } /*! * @brief SDF of Sphere. * @param [in] p Position of the tip of the ray. * @param [in] r Radius of sphere. * @return Signed Distance to the Sphere. */ float sdSphere(float3 p, float r) { return length(p) - r; } /*! * Calculate lighting. * @param [in] color Base color. * @param [in] worldPos World coordinate. * @param [in] worldNormal Normal in world space. * @param [in] atten Light attenuation. * @return Color with lighting applied. */ half4 calcLighting(half4 color, float3 worldPos, float3 worldNormal, half atten) { const float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); #ifdef USING_DIRECTIONAL_LIGHT const float3 worldLightDir = UnityWorldSpaceLightDir(worldPos); #else const float3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); #endif // USING_DIRECTIONAL_LIGHT const fixed3 lightCol = _LightColor0.rgb * atten; // Lambertian reflectance. const float nDotL = dot(worldNormal, worldLightDir); const half3 diffuse = lightCol * pow(nDotL * 0.5 + 0.5, 2.0); const half4 outColor = half4(diffuse * _Color.rgb, _Color.a); return outColor; } /*! * @brief Calculate normal of the objects. * * @param [in] p Position of the tip of the ray. * @return Normal of the objects. * @see https://iquilezles.org/articles/normalsSDF/ */ float3 getNormal(float3 p) { static const float h = 0.0001; static const float2 s = float2(1.0, -1.0); // used only for generating k. static const float3 k[4] = {s.xyy, s.yxy, s.yyx, s.xxx}; float3 normal = float3(0.0, 0.0, 0.0); UNITY_LOOP for (int i = 0; i < 4; i++) { normal += k[i] * map(p + h * k[i]); } return normalize(normal); } /*! * @brief Get depth from projected position. * @param [in] projPos Projected position. * @return Depth value. */ float getDepth(float4 projPos) { const float depth = projPos.z / projPos.w; #if defined(SHADER_API_GLCORE) \ || defined(SHADER_API_OPENGL) \ || defined(SHADER_API_GLES) \ || defined(SHADER_API_GLES3) return depth * 0.5 + 0.5; #else return depth; #endif } ENDCG Pass { Name "FORWARD_BASE" Tags { "LightMode" = "ForwardBase" } Blend Off ZTest LEqual CGPROGRAM #pragma vertex vert #pragma fragment frag // keywords: // DIRECTIONAL // LIGHTMAP_ON // DIRLIGHTMAP_COMBINED // DYNAMICLIGHTMAP_ON // LIGHTMAP_SHADOW_MIXING // VERTEXLIGHT_ON // LIGHTPROBE_SH // #pragma multi_compile_fwdbase ENDCG } // ForwardBase } }
目次
- 末尾if-break
- 末尾if-break (flatten)
- 先頭if-break
- 先頭break (flatten)
- forループ継続条件追加
- 繰り返し時ループカウンタ操作
- 末尾ループカウンタ操作
末尾if-break
まず最も典型的なマーチングループのシェーダーコードを示す. レイの衝突判定とレイの長さの上限の判定をループ末尾で行うコードと得られるDirect3D11のアセンブリコードである.
float t = 0.0; for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; if (d < _MinRayLength || t > _MaxRayLength) { break; } }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_POST_IF_BREAK -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 6 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r0.w, l(0) 4: mov r1.x, l(0) 5: loop 6: ige r1.y, r1.x, cb0[4].x 7: breakc_nz r1.y 8: mad r1.yzw, r0.xxyz, r0.wwww, v1.xxyz 9: dp3 r1.y, r1.yzwy, r1.yzwy 10: sqrt r1.y, r1.y 11: add r1.y, r1.y, l(-0.500000) 12: mad r1.z, r1.y, cb0[4].w, r0.w 13: lt r1.y, r1.y, cb0[4].y 14: lt r1.w, cb0[4].z, r1.z 15: or r1.y, r1.w, r1.y 16: if_nz r1.y 17: mov r0.w, r1.z 18: break 19: endif 20: iadd r1.x, r1.x, l(1) 21: mov r0.w, r1.z 22: endloop 23: add r1.x, -r0.w, cb0[4].z 24: lt r1.x, r1.x, l(0.000000) 25: discard_nz r1.x
このアセンブリコードを見て思った問題点は下記の2つ.
- 16行目と21行目に同じ処理がある
- 15行目と16行目の前で行ってもよいのでは?(元々のシェーダーコードがそうしているように)
if
~break
ではなく,breakc_nz
命令を生成してほしい
末尾if-break (flatten)
前述の問題を解決できるかもしれないと思い,if文に [flatten]
を指定した.
下記のコードでは UNITY_FLATTEN
としているが,これは環境差吸収用のマクロで,HLSLコンパイラ向けなら [flatten]
に置換される.
float t = 0.0; for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { break; } }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_POST_FLATTEN_IF_BREAK -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 5 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r0.w, l(0) 4: mov r1.x, l(0) 5: loop 6: ige r1.y, r1.x, cb0[4].x 7: breakc_nz r1.y 8: mad r1.yzw, r0.xxyz, r0.wwww, v1.xxyz 9: dp3 r1.y, r1.yzwy, r1.yzwy 10: sqrt r1.y, r1.y 11: add r1.y, r1.y, l(-0.500000) 12: mad r1.z, r1.y, cb0[4].w, r0.w 13: lt r1.y, r1.y, cb0[4].y 14: lt r1.w, cb0[4].z, r1.z 15: or r1.y, r1.w, r1.y 16: mov r0.w, r1.z 17: breakc_nz r1.y 18: iadd r1.x, r1.x, l(1) 19: mov r0.w, r1.z 20: endloop 21: add r1.x, -r0.w, cb0[4].z 22: lt r1.x, r1.x, l(0.000000) 23: discard_nz r1.x
breakc_nz
命令は生成されるようになったが,依然として冗長な mov
命令が生成される問題が残っている.
先頭if-break
そこでbreak判定を先頭に持っていくことにした. 初回は必ずfalseになるため,ループ末尾にif ~ breakを記述するのと意味は変わらない.
float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i++) { if (d < _MinRayLength || t > _MaxRayLength) { break; } d = map(ro + rd * t); t += d * _MarchingFactor; }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_PRE_IF_BREAK -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 6 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r1.x, l(0) 4: mov r1.y, cb0[4].z 5: mov r0.w, l(0) 6: loop 7: ige r1.z, r0.w, cb0[4].x 8: breakc_nz r1.z 9: lt r1.z, r1.y, cb0[4].y 10: lt r1.w, cb0[4].z, r1.x 11: or r1.z, r1.w, r1.z 12: if_nz r1.z 13: break 14: endif 15: mad r2.xyz, r0.xyzx, r1.xxxx, v1.xyzx 16: dp3 r1.z, r2.xyzx, r2.xyzx 17: sqrt r1.z, r1.z 18: add r1.y, r1.z, l(-0.500000) 19: mad r1.x, r1.y, cb0[4].w, r1.x 20: iadd r0.w, r0.w, l(1) 21: endloop
if内の mov
命令が消えた!
(これなら [flatten]
指定しなくても breakc_nz
命令を生成するぐらい気を利かせてもよいと思うが...)
先頭break (flatten)
[flatten]
指定をすることで breakc_nz
命令となった.
float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i++) { UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { break; } d = map(ro + rd * t); t += d * _MarchingFactor; }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_PRE_FLATTEN_IF_BREAK -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 5 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r1.x, l(0) 4: mov r1.y, cb0[4].z 5: mov r0.w, l(0) 6: loop 7: ige r1.z, r0.w, cb0[4].x 8: breakc_nz r1.z 9: lt r1.z, r1.y, cb0[4].y 10: lt r1.w, cb0[4].z, r1.x 11: or r1.z, r1.w, r1.z 12: breakc_nz r1.z 13: mad r2.xyz, r0.xyzx, r1.xxxx, v1.xyzx 14: dp3 r1.z, r2.xyzx, r2.xyzx 15: sqrt r1.z, r1.z 16: add r1.y, r1.z, l(-0.500000) 17: mad r1.x, r1.y, cb0[4].w, r1.x 18: iadd r0.w, r0.w, l(1) 19: endloop 20: add r0.w, -r1.x, cb0[4].z 21: lt r0.w, r0.w, l(0.000000) 22: discard_nz r0.w
かなり理想に近い形となったが,ループカウンタのための breakc_nz
命令と1つにまとめたい気持ちが出てくる.
forループ継続条件追加
for文の条件判定部分にbreak条件を折り込んだところ,breakc_nz
命令が1つにまとまった.
float d = _MaxLoop; for (int i = 0; i < _MaxLoop && d >= _MinRayLength && t <= _MaxRayLength; i++) { d = map(ro + rd * t); t += d * _MarchingFactor; }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_USE_LOOP_CONTINUOUS -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 63 math, 4 temp registers, 1 textures, 4 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r1.x, l(0) 4: mov r1.y, cb0[4].z 5: mov r0.w, l(0) 6: loop 7: ilt r1.z, r0.w, cb0[4].x 8: ge r1.w, r1.y, cb0[4].y 9: and r1.z, r1.w, r1.z 10: ge r1.w, cb0[4].z, r1.x 11: and r1.z, r1.w, r1.z 12: breakc_z r1.z 13: mad r2.xyz, r0.xyzx, r1.xxxx, v1.xyzx 14: dp3 r1.z, r2.xyzx, r2.xyzx 15: sqrt r1.z, r1.z 16: add r1.y, r1.z, l(-0.500000) 17: mad r1.x, r1.y, cb0[4].w, r1.x 18: iadd r0.w, r0.w, l(1) 19: endloop 20: add r0.w, -r1.x, cb0[4].z 21: lt r0.w, r0.w, l(0.000000) 22: discard_nz r0.w
欲を言うなら,先頭で判定するのは直感に反するので,末尾判断と同じようにしたいところである.
繰り返し時ループカウンタ操作
break条件を満たすとき,ループカウンタを大きな値にすることでbreakを実現するコードにしてみた.
0x7fffffffであればいかなる _MaxLoop
の値に対しても i < _MaxLoop
はfalseとなる.
i = _MaxLoop
としてもよかったのだが,後述の方法と足並を揃えるためと,即値命令の方が定数バッファへのアクセスよりよさそうだと根拠なく思ったためである.
float d = _MaxRayLength; for (int i = 0; i < _MaxLoop; i = (d < _MinRayLength || t > _MaxRayLength) ? 0x7fffffff : i + 1) { d = map(ro + rd * t); t += d * _MarchingFactor; } // for (int i = 0; i < _MaxLoop; i = d < _MinRayLength ? 0x7fffffff : i++) { // const float d = map(ro + rd * t); // t += d * _MarchingFactor; // clip(_MaxRayLength - t); // }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_USE_INDEX_UPDATE -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 4 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r0.w, l(0) 4: mov r1.x, l(0) 5: loop 6: ige r1.y, r1.x, cb0[4].x 7: breakc_nz r1.y 8: mad r1.yzw, r0.xxyz, r0.wwww, v1.xxyz 9: dp3 r1.y, r1.yzwy, r1.yzwy 10: sqrt r1.y, r1.y 11: add r1.y, r1.y, l(-0.500000) 12: mad r0.w, r1.y, cb0[4].w, r0.w 13: lt r1.y, r1.y, cb0[4].y 14: lt r1.z, cb0[4].z, r0.w 15: or r1.y, r1.z, r1.y 16: iadd r1.z, r1.x, l(1) 17: movc r1.x, r1.y, l(0x7fffffff), r1.z 18: endloop 19: add r1.x, -r0.w, cb0[4].z 20: lt r1.x, r1.x, l(0.000000) 21: discard_nz r1.x
ループカウンタの条件判断レジスタと別レジスタで movc
命令の判定を行うコードのため,命令数が1つ少なくなった.
末尾ループカウンタ操作
for文の継続条件部分,更新部分にはループカウンタに関するもの以外書きたくない気持ちがある.
そこで,下記のようにifの部分でループカウンタをいじることにしてみる.
_MaxLoop - 1
の設定でよいのだが,1の減算命令が生成されてしまうかもしれないため,int型の最大値 - 1を設定することにしている.
(実際には _MaxLoop - 1
でも減算命令は生成されなかった)
for (int i = 0; i < _MaxLoop; i++) { const float d = map(ro + rd * t); t += d * _MarchingFactor; UNITY_FLATTEN if (d < _MinRayLength || t > _MaxRayLength) { i = 0x7ffffffe; } }
Global Keywords: <none> Local Keywords: _BREAKMETHOD_POST_UPDATE_INDEX -- Vertex shader for "d3d11": // No shader variant for this keyword set. The closest match will be used instead. -- Hardware tier variant: Tier 1 -- Fragment shader for "d3d11": // Stats: 62 math, 4 temp registers, 1 textures, 4 branches 0: dp3 r0.x, v2.xyzx, v2.xyzx 1: rsq r0.x, r0.x 2: mul r0.xyz, r0.xxxx, v2.xyzx 3: mov r0.w, l(0) 4: mov r1.x, l(0) 5: loop 6: ige r1.y, r1.x, cb0[4].x 7: breakc_nz r1.y 8: mad r1.yzw, r0.xxyz, r0.wwww, v1.xxyz 9: dp3 r1.y, r1.yzwy, r1.yzwy 10: sqrt r1.y, r1.y 11: add r1.y, r1.y, l(-0.500000) 12: mad r0.w, r1.y, cb0[4].w, r0.w 13: lt r1.y, r1.y, cb0[4].y 14: lt r1.z, cb0[4].z, r0.w 15: or r1.y, r1.z, r1.y 16: iadd r1.z, r1.x, l(1) 17: movc r1.x, r1.y, l(0x7fffffff), r1.z 18: endloop 19: add r1.x, -r0.w, cb0[4].z 20: lt r1.x, r1.x, l(0.000000) 21: discard_nz r1.x
繰り返し時ループカウンタ操作と同じコードとなった.
可読性を重んじるならこのコードにすべきかもしれない.
あと,d
の宣言をループ内に持ってこれる点も大きい.
まとめ
HLSLコンパイラはループ内のbreakの扱いが下手であり,特に末尾にif ~ breakを書いた場合,かなり冗長なコードを生成することがわかった.
本記事で示した各手法について,アセンブリ中のmathとbranch数を示すと下記の通り. 基本的にこの値が小さいほどよいコードであると言えると思う.
手法 | math | branches |
---|---|---|
末尾if-break | 62 | 6 |
末尾if-break (flatten) | 62 | 5 |
先頭if-break | 62 | 6 |
先頭break (flatten) | 62 | 5 |
forループ継続条件追加 | 63 | 4 |
繰り返し時ループカウンタ操作 | 62 | 4 |
末尾ループカウンタ操作 | 62 | 4 |
branchは if ~ endif
で1つ, breakc_nz
命令で1つ計上されているようだ.
この値から繰り返し時ループカウンタ操作,または末尾ループカウンタ操作の出力アセンブリが良いアセンブリと言えるのではないだろうか?