2011-06-29 35 views
13

Tôi có một raytracer đơn giản trong python. dựng hình ảnh 200x200 mất 4 phút, điều đó chắc chắn là quá nhiều đối với khẩu vị của tôi. Tôi muốn cải thiện tình hình.Cải thiện hiệu suất của chức năng đánh bóng raytracing

Một số điểm: Tôi chụp nhiều tia cho mỗi pixel (để cung cấp tính năng chống răng cưa) với tổng cộng 16 tia mỗi pixel. 200x200x16 là tổng cộng 640000 tia. Mỗi tia phải được kiểm tra tác động lên nhiều đối tượng Sphere trong cảnh. Ray cũng là một đối tượng chứ không phải tầm thường

class Ray(object): 
    def __init__(self, origin, direction): 
     self.origin = numpy.array(origin) 
     self.direction = numpy.array(direction) 

Sphere là hơi phức tạp hơn, và mang logic cho hit/nohit:

class Sphere(object): 
    def __init__(self, center, radius, color): 
     self.center = numpy.array(center) 
     self.radius = numpy.array(radius) 
     self.color = color 

    @profile 
    def hit(self, ray): 
     temp = ray.origin - self.center 
     a = numpy.dot(ray.direction, ray.direction) 
     b = 2.0 * numpy.dot(temp, ray.direction) 
     c = numpy.dot(temp, temp) - self.radius * self.radius 
     disc = b * b - 4.0 * a * c 

     if (disc < 0.0): 
      return None 
     else: 
      e = math.sqrt(disc) 
      denom = 2.0 * a 
      t = (-b - e)/denom 
      if (t > 1.0e-7): 
       normal = (temp + t * ray.direction)/self.radius 
       hit_point = ray.origin + t * ray.direction 
       return ShadeRecord.ShadeRecord(normal=normal, 
               hit_point=hit_point, 
               parameter=t, 
               color=self.color)   

      t = (-b + e)/denom 

      if (t > 1.0e-7): 
       normal = (temp + t * ray.direction)/self.radius    hit_point = ray.origin + t * ray.direction 
       return ShadeRecord.ShadeRecord(normal=normal, 
               hit_point=hit_point, 
               parameter=t, 
               color=self.color)  

     return None  

Bây giờ, tôi chạy một số hồ sơ, và nó xuất hiện rằng việc xử lý dài nhất thời gian nằm trong hàm hit()

ncalls tottime percall cumtime percall filename:lineno(function) 
    2560000 118.831 0.000 152.701 0.000 raytrace/objects/Sphere.py:12(hit) 
    1960020 42.989 0.000 42.989 0.000 {numpy.core.multiarray.array} 
     1 34.566 34.566 285.829 285.829 raytrace/World.py:25(render) 
    7680000 33.796 0.000 33.796 0.000 {numpy.core._dotblas.dot} 
    2560000 11.124 0.000 163.825 0.000 raytrace/World.py:63(f) 
    640000 10.132 0.000 189.411 0.000 raytrace/World.py:62(hit_bare_bones_object) 
    640023 6.556 0.000 170.388 0.000 {map} 

Điều này không làm tôi ngạc nhiên và tôi muốn giảm giá trị này càng nhiều càng tốt. Tôi vượt qua để lót hồ sơ, và kết quả là

Line #  Hits   Time Per Hit % Time Line Contents 
============================================================== 
    12            @profile 
    13            def hit(self, ray): 
    14 2560000  27956358  10.9  19.2   temp = ray.origin - self.center 
    15 2560000  17944912  7.0  12.3   a = numpy.dot(ray.direction, ray.direction) 
    16 2560000  24132737  9.4  16.5   b = 2.0 * numpy.dot(temp, ray.direction) 
    17 2560000  37113811  14.5  25.4   c = numpy.dot(temp, temp) - self.radius * self.radius 
    18 2560000  20808930  8.1  14.3   disc = b * b - 4.0 * a * c 
    19             
    20 2560000  10963318  4.3  7.5   if (disc < 0.0): 
    21 2539908  5403624  2.1  3.7    return None 
    22             else: 
    23  20092  75076  3.7  0.1    e = math.sqrt(disc) 
    24  20092  104950  5.2  0.1    denom = 2.0 * a 
    25  20092  115956  5.8  0.1    t = (-b - e)/denom 
    26  20092  83382  4.2  0.1    if (t > 1.0e-7): 
    27  20092  525272  26.1  0.4     normal = (temp + t * ray.direction)/self.radius 
    28  20092  333879  16.6  0.2     hit_point = ray.origin + t * ray.direction 
    29  20092  299494  14.9  0.2     return ShadeRecord.ShadeRecord(normal=normal, hit_point=hit_point, parameter=t, color=self.color) 

Vì vậy, dường như phần lớn thời gian là chi tiêu trong đoạn mã này:

 temp = ray.origin - self.center 
     a = numpy.dot(ray.direction, ray.direction) 
     b = 2.0 * numpy.dot(temp, ray.direction) 
     c = numpy.dot(temp, temp) - self.radius * self.radius 
     disc = b * b - 4.0 * a * c 

ở đâu tôi không thực sự nhìn thấy rất nhiều để tối ưu hóa. Bạn có bất kỳ ý tưởng làm thế nào để làm cho mã này hiệu quả hơn mà không đi C?

+3

+1 Rất được trình bày. Tôi không biết Python, do đó, như một câu hỏi: là numpy.dot gọi xuống để thực hiện một C? Nếu không, có lẽ bạn có thể cải thiện tốc độ bằng cách thực hiện tính toán sản phẩm chấm thủ công. – Phrogz

+0

Có, được thực hiện một cách gọn gàng trong C. Đó là lý do tại sao tôi có cảm giác không có nhiều để đạt được để thực hiện lại hàm hit trong C. –

+0

là các vectơ đơn vị vectơ hướng của bạn? bạn có thể biến chúng thành các vector đơn vị trong '__init__' không? nếu vậy, phép toán sản phẩm chấm của bạn trở nên đơn giản hơn. – underrun

Trả lời

1

Với mã như thế này, bạn có thể hưởng lợi từ việc ghi nhớ các biểu thức con phổ biến như self.radius * self.radius làm self.radius21/self.radiusself.one_over_radius. Chi phí của trình thông dịch python có thể thống trị những cải tiến tầm thường như vậy.

+0

Tôi đã thử. ý tưởng hay, nhưng nó không thay đổi nhiều. Tôi nhận ra rằng tôi đã làm một lỗi trong việc sử dụng self.radius như một mảng numpy, thay vì một phao đơn giản. Một lần nữa, không phải là một sự thay đổi có thể đo lường được. Tôi đang xem xét việc thay đổi thiết kế và cố gắng giảm số lượng cuộc gọi, nhưng tôi nghi ngờ có nhiều thứ để đạt được hiệu suất và rất nhiều thứ bị mất trong sự rõ ràng của mã. Tôi nghĩ tôi sẽ phải đi song song sớm thôi. –

+0

Đúng vậy. Việc triển khai C sẽ tính toán những điều đó nhanh hơn so với Python VM có thể tìm nạp một mã opcode. Chúng là những tối ưu hóa hợp lệ và sẽ cho thấy những cải tiến nhỏ nhưng có thể đo lường là một chương trình C hoạt động tốt. – phkahler

7

1) Ray tracing là vui vẻ nhưng nếu bạn quan tâm ở tất cả về hiệu suất, bãi python và chuyển sang C. Không C++ trừ khi bạn là một số loại siêu chuyên gia, chỉ cần C.

2) Chiến thắng lớn trong cảnh với nhiều (20 hoặc nhiều hơn) đối tượng là sử dụng một chỉ số không gian để giảm số lượng các bài kiểm tra giao lộ. Các tùy chọn phổ biến là kD-trees, OctTrees, AABB.

3) Nếu bạn nghiêm túc, hãy xem ompf.org - đó là tài nguyên cho việc này.

4) Đừng đi ompf với python hỏi về tối ưu hóa - hầu hết mọi người có thể chụp 1 triệu đến 2 triệu tia mỗi giây thông qua cảnh trong nhà với 100 nghìn hình tam giác ... Mỗi lõi.

Tôi thích Python và theo dõi tia, nhưng sẽ không bao giờ xem xét việc đặt chúng lại với nhau. Trong trường hợp này, tối ưu hóa chính xác là chuyển đổi ngôn ngữ.

+0

+1 Tôi đồng ý hoàn toàn với điều này. Python có thể được tốt đẹp cho prototyping, nhưng một khi bạn đã chứng minh thuật toán của bạn và làm quan tâm về hiệu suất đó là thời gian cho C/C + + (hoặc một cái gì đó mà biên dịch để thích hợp nhị phân, không kid mình một số VM/GC ngôn ngữ sẽ làm). Bởi tất cả các phương tiện sau đó quấn tia tracer lên như là một thành phần python và sử dụng nó trong các hệ thống cấp cao hơn. – timday

+0

@timday - khoảng năm 1995 tôi đã thực hiện một tia truy tìm thành phần OLE trong C++ (bất cứ điều gì họ được gọi là sau đó) và đã chuyển động máy ảnh từ một ứng dụng Visual Basic. Hình ảnh 120x90 pixel được nhiều FPS tôi đã xem xét gói PyGame xung quanh thư viện của tôi :-) không có thời gian cho nó ... – phkahler

+0

Tôi nghĩ rằng C + + là tốt cho việc viết mã hiệu suất cao raytracing những ngày này. Trong thực tế, tôi làm điều đó để kiếm sống. Bạn chỉ cần tránh sử dụng các phần chậm chạp của C++, như ngoại lệ và STL. – Crashworks

1

Một tối ưu hóa nhỏ là: ab * b luôn tích cực, vì vậy disc < 0.0 là đúng nếu (c > 0 && b < min(a, c)). Trong trường hợp này, bạn có thể tránh tính toán b * b - 4.0 * a * c. Với hồ sơ bạn đã làm điều này sẽ có nhiều nhất cạo 7% thời gian chạy hit tôi đoán.

1

Đặt cược tốt nhất của bạn sẽ là sử dụng các bảng tra cứu và các giá trị được tính toán trước càng nhiều càng tốt.

Khi câu trả lời của bạn cho biết rằng vectơ hướng tia của bạn là vectơ đơn vị, trong phần quan trọng bạn liệt kê bạn có thể tạo ít nhất một tối ưu hóa ngay lập tức.Bất kỳ chấm vectơ nào cũng có chiều dài bình phương sao cho một chấm vectơ đơn vị sẽ luôn là 1.

Ngoài ra, bán kính tính trước bình phương (trong chức năng __init__ của hình cầu).

Sau đó, bạn đã có:

temp = ray.origin - self.center 
a = 1 # or skip this and optimize out later 
b = 2.0 * numpy.dot(temp, ray.direction) 
c = numpy.dot(temp, temp) - self.radius_squared 
disc = b * b - 4.0 * c 

tạm thời chấm tạm thời sẽ cung cấp cho bạn tương đương với sum(map(lambda component: component*component, temp)) ... tôi không chắc đó là nhanh hơn mặc dù.

+0

đã làm một số xét nghiệm - numpy.dot() là nhanh hơn nhiều so với tổng (map()). – underrun

6

Nhìn vào mã của bạn, có vẻ như vấn đề chính của bạn là bạn có các dòng mã đang được gọi là 2560000 lần. Điều đó sẽ có xu hướng mất nhiều thời gian bất kể bạn đang làm công việc gì trong mã đó. Tuy nhiên, bằng cách sử dụng numpy, bạn có thể tổng hợp rất nhiều công việc này vào một số lượng nhỏ các cuộc gọi gắt gỏng.

Điều đầu tiên cần làm là kết hợp các tia của bạn với nhau thành các mảng lớn. Thay vì sử dụng đối tượng Ray có vectơ 1x3 cho nguồn gốc và hướng sử dụng các mảng Nx3 có tất cả các tia mà bạn cần để phát hiện lần truy cập. Đỉnh của chức năng hit của bạn sẽ kết thúc lên trông như thế này:

temp = rays.origin - self.center 
b = 2.0 * numpy.sum(temp * rays.direction,1) 
c = numpy.sum(numpy.square(temp), 1) - self.radius * self.radius 
disc = b * b - 4.0 * c 

Đối với phần tiếp theo bạn có thể sử dụng

possible_hits = numpy.where(disc >= 0.0) 
a = a[possible_hits] 
disc = disc[possible_hits] 
... 

để tiếp tục chỉ với các giá trị mà vượt qua bài kiểm tra biệt thức. Bạn có thể dễ dàng nhận được đơn đặt hàng của cải tiến hiệu suất cường độ theo cách này.

Các vấn đề liên quan