تحسين تكرار لعبة الحياة على مجموعة 80x60 RGB بكسل
-
19-09-2019 - |
سؤال
حسنا، لذلك لدي قطعة من رمز بيثون الذي يحتاج حقا إلى تحسين.
- إنها عملية تكرار في الحياة على صورة صغيرة (80x60 بكسل) واستخراج قيم RGB منه.
- حاليا باستخدام متداخلة للحلقات؛ أنا أفضل مبادلة من تلك للحلقات بشكل أسرع
map()
وظيفة C، ولكن إذا قمت بذلك، فلا يمكنني معرفة كيف يمكنني الحصول على القيم x، y، ولا القيم المحلية المحددة من نطاق الوظائف التي أحتاج إلى تحديدها. - سوف تستخدم
map()
أن تكون أسرع من هذه المجموعة الحالية للحلقات؟ كيف يمكنني استخدامه وما زلت أحصل على X، Y؟ - أستخدم حاليا أسطح Pygame، وقد جربت
surfarray/pixelarray
الوحدات، ولكن منذ أن أغير / الحصول على كل بكسل، فإنه أبطأ كثيرا منSurface.get_at()/set_at()
. - أيضا، غير ذي صلة قليلا ... هل تعتقد أن هذا يمكن أن يكون أسرع إذا لم يكن بيثون عبور قائمة بالأرقام ولكن فقط زيادة عدد، مثل بلغات أخرى؟ لماذا لا تشمل Python طبيعيا () بالإضافة إلى توقعاتهم ()؟
- كمية الشرطية هناك ربما تجعل الأمور أبطأ أيضا، أليس كذلك؟ الجزء الأبطأ هو التحقق من الجيران (حيث يبني القائمة N) ... استبدلت هذا بت كامل مع شريحة وصول على صفيف ثنائي الأبعاد، لكنه لا يعمل بشكل صحيح.
نسخة منقوشة من التعليمات البرمجية:
xr = xrange(80)
yr = xrange(60)
# surface is an instance of pygame.Surface
get_at = surface.get_at()
set_at = surface.set_at()
for x in xr:
# ....
for y in yr:
# ...
pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]
# ... more complex stuff here which changes R,G,B values independently of each other
set_at((x,y),(pixelR,pixelG,pixelB))
النسخة الكاملة من الوظيفة:
# xr, yr = xrange(80), xrange(60)
def live(surface,xr,yr):
randint = random.randint
set_at = surface.set_at
get_at = surface.get_at
perfect = perfectNeighbours #
minN = minNeighbours # All global variables that're defined in a config file.
maxN = maxNeighbours #
pos = actual # actual = (80,60)
n = []
append = n.append
NEIGHBOURS = 0
for y in yr: # going height-first for aesthetic reasons.
decay = randint(1,maxDecay)
growth = randint(1,maxGrowth)
for x in xr:
r, g, b, a = get_at((x,y))
del n[:]
NEIGHBOURS = 0
if x>0 and y>0 and x<pos[0]-1 and y<pos[1]-1:
append(get_at((x-1,y-1))[1])
append(get_at((x+1,y-1))[1])
append(get_at((x,y-1))[1])
append(get_at((x-1,y))[1])
append(get_at((x+1,y))[1])
append(get_at((x-1,y+1))[1])
append(get_at((x+1,y+1))[1])
append(get_at((x,y+1))[1])
for a in n:
if a > 63:
NEIGHBOURS += 1
if NEIGHBOURS == 0 and (r,g,b) == (0,0,0): pass
else:
if NEIGHBOURS < minN or NEIGHBOURS > maxN:
g = 0
b = 0
elif NEIGHBOURS==perfect:
g += growth
if g > 255:
g = 255
b += growth
if b > growth: b = growth
else:
if g > 10: r = g-10
if g > 200: b = g-100
if r > growth: g = r
g -= decay
if g < 0:
g = 0
b = 0
r -= 1
if r < 0:
r = 0
set_at((x,y),(r,g,b))
المحلول
منذ أن تقرأ وإعادة كتابة كل بكسل، أعتقد أنه يمكنك الحصول على أفضل تحسن السرعة من خلال عدم استخدام Surface
.
أقترح أولا أخذ صورتك 80x60 وتحويلها إلى ملف نقطي عادي مع بكسل 32 بت. ثم اقرأ بيانات البكسل في بيثون array
هدف. الآن يمكنك المشي فوق array
الكائن وقيم القراءة وحساب القيم الجديدة ومثبت القيم الجديدة في مكانها بأقصى سرعة. عند الانتهاء، احفظ صورتك النقطية الجديدة، ثم تحويلها إلى Surface
.
يمكنك أيضا استخدام وحدات البكسل 24 بت، ولكن يجب أن تكون أبطأ. البكسل 32 بت يعني بكسل واحد هو قيمة عدد صحيح 32 بت، مما يجعل مجموعة من البكسل أسهل بكثير للفهرس. البكسلات المعبأة 24 بت يعني أن كل بكسل هو 3 بايت، وهو أكثر مزعجا إلى الفهرس.
أعتقد أنك ستكتسب أكثر سرعة من هذا النهج من خلال محاولة تجنب استخدام for
. وبعد إذا جربت هذا، فيرجى نشر شيء ما هنا لإعلامنا مدى جودة عمله أو لم يفعله. حظ سعيد.
تحرير: اعتقدت أن array
لديه فقط مؤشر واحد. لست متأكدا من كيفية تمكنك من الحصول على مؤشرين للعمل. كنت أتوقع منك أن تفعل شيئا مثل هذا:
def __i(x, y):
assert(0 <= x < 80)
assert(0 <= y < 60)
i = (y*80 + x) * 4
return i
def red(x, y):
return __a[__i(x, y)]
def green(x, y):
return __a[__i(x, y) + 1]
def blue(x, y):
return __a[__i(x, y) + 2]
def rgb(x, y):
i = __i(x, y)
return __a[i], __a[i + 1], __a[i + 2]
def set_rgb(x, y, r, g, b):
i = __i(x, y)
_a[i] = r
_a[i + 1] = g
_a[i + 2] = b
# example:
r, g, b = rgb(23, 33)
منذ بيثون array
يمكن أن تعقد فقط نوع واحد، ستحتاج إلى تعيين النوع إلى "البايت غير الموقعة" ثم فهرس كما أظهرت.
حيث بالطبع __a
هو الفعلي array
عامل.
إذا لم يكن أي من هذا مفيدا، فحاول تحويل النقطات النقطية إلى قائمة، أو ربما ثلاث قوائم. يمكنك استخدام قوائم متداخلة للحصول على عنوان ثنائي الأبعاد.
آمل أن يساعد هذا. إذا لم يكن مفيدا، فأنا لا أفهم ما تفعله؛ إذا شرحت المزيد سأحاول تحسين الإجابة.
نصائح أخرى
ما الذي يجعل الرمز الخاص بك بطيء ربما لا الحلقات، فهي سريعة بشكل لا يصدق.
ما يطالب الكود الخاص بك هو عدد مكالمات الدالة. علي سبيل المثال
pixelR = get_at((x,y))[0]
pixelG = get_at((x,y))[1]
pixelB = get_at((x,y))[2]
يكون كثير أبطأ من (حوالي 3 مرات أعتقد)
r, g, b, a = get_at((x,y))
كل get_at
, set_at
Call Locks السطح، وبالتالي فإنه أسرع الوصول مباشرة إلى البكسل باستخدام الأساليب المتاحة. الشخص الذي يبدو أكثر معقولة هو Surface.get_buffer
.
استخدام map
لا يعمل في مثالك، لأنك تحتاج إلى الفهارس. مع عدد قليل من 80 و 60 أرقاما قد تكون أسرع للاستخدام range()
بدلا من xrange()
.
map(do_stuff, ((x, y) for x in xrange(80) for y in xrange(60)))
أين do_stuff
من المفترض أن تكون محددة مثل ذلك:
def do_stuff(coords):
r, g, b, a = get_at(coords)
# ... whatever you need to do with those ...
set_at(coords, (r, g, b))
يمكنك بدلا من ذلك استخدام فهم قائمة بدلا من تعبير المولدات كوسيطة ثانية ل map
(يستبدل ((x, y) ...)
مع [(x, y) ...]
) واستخدام range
بدلا من xrange
. وبعد أود أن أقول أنه ليس من المرجح أن يكون له تأثير كبير على الأداء، رغم ذلك.
يحرر: لاحظ أن GS هو بالتأكيد الحق في for
لا يجري حلقات عدم وجود الشيء الرئيسي في الحاجة إلى التحسين في التعليمات البرمجية الخاصة بك ... قلل على المكالمات الزائدة get_at
هو أكثر أهمية. في الواقع، لست متأكدا مما إذا استبدل الحلقات map
سوف يحسن في الواقع الأداء هنا على الإطلاق ... بعد أن قال ذلك، أجد map
الإصدار أكثر قراءة (ربما بسبب خلفية FP الخاصة بي ...)، لذلك هنا تذهب على أي حال. ؛-)