跳到內容

Blender 3D: 融入 Python/最佳化

來自華夏公益教科書

為 Blender:Python 編寫最佳匯出器和匯入器

[編輯 | 編輯原始碼]

如果您剛開始編寫指令碼,不要過於重視這些指南,最好先讓程式碼執行起來,然後在您開始尋找最佳化區域之前,發現對最佳化的需求。

如果您正在為其他人制作工具,請記住他們可能會使用比您測試的更大的資料集。因此,在釋出之前,最好嘗試最佳化一下。

有關更通用的 Python 效能提示,請參閱 http://wiki.python.org/moin/PythonSpeed/PerformanceTips

獲取物件(匯出/一般 Blender)

[編輯 | 編輯原始碼]

Blender.Object.Get()通常用於獲取所有要匯出的物件。這是不好的做法,因為Object.Get()將返回 Blender 中每個場景中的物件,這幾乎永遠不是使用者想要的,並且會導致資料重疊,但如果使用者擁有 2 個或更多個大型場景,消耗大量記憶體,也會花費很長時間。

相反,使用Blender.Scene.GetCurrent().getChildren()這將返回當前場景中的所有物件。

另一種選擇是使用Blender.Object.GetSelected()這將返回當前場景中可見層上的選定物件。

獲取網格資料(僅匯出)

[編輯 | 編輯原始碼]

網格不僅僅是像 Blender 中大多數其他型別那樣的薄包裝器,因此您想要避免meshObject.getData()meshObject.data儘可能多地,最好只調用每個網格資料一次。

要獲取物件引用的資料的名稱,請不要使用 ob.getData().name 只是為了獲取物件的名稱。相反,使用obj.getData(1)…這意味著obj.getData(1=name_only)這很好,因為它適用於所有資料型別,只是

請注意,最近(截至 15/10/05)添加了 Mesh 模組(Blender 網格資料的薄包裝器),這意味著您可以執行meshObject.getData(mesh=1)不會出現 NMesh 問題,但這是新的,不支援所有 NMesh 函式。

使用新的 python 功能 mesh.transform()(僅匯出)

[編輯 | 編輯原始碼]

從 Blender 2.37 開始nmesh.transform(ob.matrix)可用於透過 4x4 變換矩陣變換 NMesh。
在我的系統上,它比在 python 中透過矩陣變換每個頂點的座標和法線快 100 多倍。雖然在我的 obj 匯出器中使用它所獲得的整體速度約為 5%。

確保如果您要匯出頂點法線,請新增 1,
mesh.transform(matrix, recalc_normals=1)這將避免不必要的頂點法線變換。

注意 python 佔用大量記憶體。(一般情況下)

[編輯 | 編輯原始碼]

對於小型應用程式,您可以忽略這一點,但我發現對於大型場景,如果早期沒有考慮到,這可能會成為問題。


Python 中的列表(以及因此 blender:python)永遠不會被釋放,但 python 可以在指令碼執行時重新使用記憶體。基本上,在 python 中建立大型列表和大量遞迴變數賦值會洩漏記憶體,只有透過重新啟動才能重新獲得。 - *如果您能 - 避免建立大型列表。*

這在 Python 2.4.1 中已修復,該版本將與 blender 編譯在一起。

列表(Python 通用)

[編輯 | 編輯原始碼]

列表查詢

[編輯 | 編輯原始碼]

在 Python 中,有一些巧妙的列表函式可以幫助您避免搜尋整個列表。
即使您沒有迴圈遍歷列表資料,python 也會,因此您需要注意會透過搜尋整個列表來減慢指令碼速度的函式。

myList.count(listItem)
myList.index(listItem)
myList.remove(listItem)
if listItem in myList: ...

上述函式對列表執行完整的迴圈,因此,如果您能夠避免在大型列表上使用它們,指令碼將執行得更快。

修改列表

[編輯 | 編輯原始碼]

在 python 中,我們可以向列表新增和刪除專案。當列表在列表開頭修改長度時,這會比較慢,因為修改後的索引之後的所有資料都需要向上或向下移動 1 個位置。

當然,向列表末尾新增專案的簡單方法是使用myList.append(listItem)myList.extend(someList)刪除專案的最快方法是myList.pop()

要使用索引,您可以使用myList.insert(index, listItem)而 pop 也接受索引進行列表刪除,但這些操作速度較慢。

有時,重新構建列表速度更快(但更佔記憶體)。
假設我們要從列表中刪除所有三角形面。

而不是

fIdx = len(mesh.faces) # Loop backwards
while fIdx: # While the value is not 0
	fIDx -=1

	if len(mesh.faces[fIDx].v) == 3:
		mesh.pop(fIdx) # Remove the tri

使用列表推導構建新列表速度更快。

mesh.faces = [f for f in mesh.faces if len(f.v) != 3]

新增列表項

[編輯 | 編輯原始碼]

如果您有一個要新增到另一個列表的列表,而不是在 for 迴圈中追加,請使用

myList.extend([li1, li2...])

而不是...

for l in someList:
	myList.append(l)

請注意,insert 可以在需要時使用,但它比 append 速度慢,尤其是在向長列表開頭插入時。
此示例顯示了一種建立反轉列表的非常次優方法。

for l in someList:
	myList.insert(0,l)


刪除列表項

[編輯 | 編輯原始碼]

使用myList.pop(i)而不是myList.remove(listItem)
這要求您擁有列表項的索引,但速度更快,因為 remove 需要搜尋列表。
使用 pop 刪除列表項時,迴圈應優先選擇 while 迴圈而不是 for 迴圈。

以下是如何在一次迴圈中刪除項的示例,先刪除最後一個項,這樣更快(如上所述)。

listIndex = len(myList)
while listIndex:
	listIndex -=1
	if myList[listIndex].someTestProperty == 1:
		myList.pop(listIndex)


一種快速刪除項的方法是交換兩個列表項,以便要刪除的項始終在最後,這樣可以打亂列表順序。

popIndex = 5

# Swap so the popIndex is last.
myList[-1], myList[popIndex] = myList[popIndex], myList[-1]

# Remove last item (popIndex)
myList.pop()

在大型列表中刪除多個項時,這可以提高速度。

避免複製列表

[編輯 | 編輯原始碼]

將列表/字典傳遞給函式時,最好讓函式修改列表,而不是返回一個新列表。 這意味著 Python 不需要在記憶體中建立新列表。
就地修改列表的函式比建立新列表的函式更有效。
normalize(vec)更快:沒有重新賦值...比...快。
vec = normalize(vec)更慢,僅用於建立新的、未連結列表的函式。

還要注意,傳遞切片列表會在 Python 記憶體中建立列表的副本,例如...
foobar(mylist[4:-1])
如果 mylist 是一個包含大量浮點數的大型列表,則複製可能會佔用大量額外的記憶體。

字典(Python 通用)

[編輯 | 編輯原始碼]

字典查詢

[編輯 | 編輯原始碼]

當您訪問字典項時,someDict['foo'] 會執行查詢操作,Python 的查詢速度非常快,但如果您在迴圈中訪問這些資料,最好建立一個可以更快引用的變數。

for key in someDict.keys():
...wip

路徑名(匯出)

[編輯 | 編輯原始碼]

Blender 路徑名與其他通用 Python 模組中的路徑名不相容。
Python 的原生函式不理解 Blender 的 //(代表 Blender 中的當前檔案目錄)和 #(代表當前幀號)。
一個常見的示例是匯出場景時需要將影像路徑一起匯出。
Blender.sys.expandpath(img.filename)
將為您提供絕對路徑,因此您可以將該路徑傳遞給其他 Python 模組中的函式。

正確的字串解析(匯入/匯出)

[編輯 | 編輯原始碼]

由於許多檔案格式是 ASCII 格式,因此您解析/匯出字串的方式會對程式執行速度產生很大影響。
將字串匯入 Blender 時,有幾種解析字串的方法。

傳遞字串

[編輯 | 編輯原始碼]

使用 float(string) 而不是 eval(string),如果您知道該值將是整數,則可以使用 int(string),float() 也適用於整數,但如果整數被轉換為整數,則速度更快。

檢查字串開頭/結尾

[編輯 | 編輯原始碼]

如果您要檢查字串開頭是否有關鍵字,請使用...

if line.startswith('vert '):

...而不是

if line[0:5] == 'vert ':


使用 Startswith 速度略快(大約 5%)。
myString.endswith('foobar') 也可以用於行尾。
此外,如果您不確定文字是使用大寫還是小寫,請使用 lower 或 upper 字串函式。 例如:if line.upper().startswith('VERT ')

將字串寫入檔案(Python 通用)

[編輯 | 編輯原始碼]

以下列出了將多個字串連線成一個字串以供寫入的 3 種方法
這實際上適用於程式碼中任何涉及大量字串連線的部分。

Python 的字串加法。 如果可以避免,請不要使用,尤其是在主要的 資料寫入迴圈中。

file.write(str1 + ' ' + str2 + ' ' + str3 + '\n')


字串格式化。 當您從浮點數和整數寫入字串資料時,請使用此方法。

file.write('%s %s %s\n' % (str1, str2, str3))


Python 的字串連線函式。 用於連線字串列表。

file.write(' '.join([str1, str2, str3, '\n']))


join 在處理多個字串時速度最快,字串格式化速度也很快(更適合轉換資料型別)。 字串運算速度最慢。

分析您的程式碼(Python 通用)

[編輯 | 編輯原始碼]

只需對程式碼的部分進行計時,就可以找到需要最佳化的部分。 例如

time1 = Blender.sys.time()

for i in range(10):
	x = i*2

print Blender.sys.time() - time1

Psyco JIT 編譯器(Python 通用)

[編輯 | 編輯原始碼]

存在一個名為 Psyco 的 Python 模組,可以動態編譯一些 Python 函式。 大多數速度提升都會影響用 Python 編寫的演算法,因此匯入器和匯出器比處理 3D 數學的指令碼從這些演算法中獲得的收益要少。

對於許多指令碼,使用 Psyco 的最簡單方法是執行以下操作。

import psyco
psyco.full()

Psyco 還可以分析您的程式碼。

import psyco
psyco.profile()


為了方便指令碼分發,最好嘗試載入 Psyco,以避免引發錯誤。

try:
	import psyco
	psyco.full()
except:
	print 'For optimal performance on an x86 CPU, install psyco'

注意,Psyco 報告使用大量系統記憶體,如果記憶體不足,最好不要使用 Psyco。

謹慎使用 Try/Except

[編輯 | 編輯原始碼]

try 函式有助於節省編寫程式碼以檢查條件的時間,
但是 'try' 比 'if' 慢大約 10 倍,因此不要在程式碼中執行多次迴圈(1000 次或更多次)的區域使用 'try'。

在某些情況下,使用 'try' 比檢查條件是否會引發錯誤更快,因此值得嘗試一下。

Python 物件

[編輯 | 編輯原始碼]

使用 "is" 而不是 "=="

[編輯 | 編輯原始碼]

在某些情況下,可以使用 "is" 而不是 "==" 進行比較,使用 "is not" 而不是 "!="。
"==" 檢查兩個變數的值是否相同,而 "is" 則測試 Python 物件是否相同,是否共享相同的記憶體——是否都是同一個物件的例項。(Python 物件,而不是 Blender 的物件)
使用 "is" 的優勢在於速度更快,因為它不需要比較那麼多資料。

以下是一個基準測試,比較 Python 類和 Blender 向量——一個是相同的,另一個是不同的。

from Blender import *

print '\n\nStarting benchmark.'

# Test class
class a:
        pass

class1 = a()
class2 = a()
vec1 = Mathutils.Vector(1,2,3)
vec2 = Mathutils.Vector(1,2,4)

f = 400000

t = sys.time()
for i in range(f):
        class1 is class1
        class1 is class2
        vec1 is vec1
        vec1 is vec2
print ' Is Banchmark %.6f' % (sys.time() - t)


t = sys.time()
for i in range(f):
        class1 == class1
        class1 == class2
        vec1 == vec1
        vec1 == vec2

print ' == Benchmark %.6f' % (sys.time() - t)
print 'Done\n'

我的電腦顯示了以下結果。 我應該進行更多測試,但這足以代表其他測試。

Starting benchmark.
 Is Banchmark 0.284363
 == Benchmark 3.367173
Done


使用 vec1、vec2、class1、class2 作為整數和浮點數進行相同的測試。
"is" 仍然更快。

Starting benchmark.
 Is Banchmark 0.272853
 == Benchmark 0.322123
Done


華夏公益教科書