2009-12-01 42 views
11

Sản phẩm chấm của hai vectơ n chiều u=[u1,u2,...un]v=[v1,v2,...,vn] được cho bởi u1*v1 + u2*v2 + ... + un*vn.Sản phẩm chấm được tối ưu hóa trong Python

Câu hỏi posted yesterday khuyến khích tôi tìm cách nhanh nhất để tính toán các sản phẩm chấm trong Python chỉ sử dụng thư viện chuẩn, không có mô-đun bên thứ ba hoặc C/Fortran/C++.

Tôi đã định thời gian bốn phương pháp khác nhau; cho đến nay nhanh nhất có vẻ là sum(starmap(mul,izip(v1,v2))) (trong đó starmapizip đến từ mô-đun itertools).

Đối với mã trình bày dưới đây, đó là những lần trôi qua (tính bằng giây, cho một triệu chạy):

d0: 12.01215 
d1: 11.76151 
d2: 12.54092 
d3: 09.58523 

bạn có thể nghĩ ra một cách nhanh hơn để làm điều này?

import timeit # module with timing subroutines                
import random # module to generate random numnbers               
from itertools import imap,starmap,izip 
from operator import mul 

def v(N=50,min=-10,max=10): 
    """Generates a random vector (in an array) of dimension N; the           
    values are integers in the range [min,max].""" 
    out = [] 
    for k in range(N): 
     out.append(random.randint(min,max)) 
    return out 

def check(v1,v2): 
    if len(v1)!=len(v2): 
     raise ValueError,"the lenght of both arrays must be the same" 
    pass 

def d0(v1,v2): 
    """                          
    d0 is Nominal approach:                     
    multiply/add in a loop                     
    """ 
    check(v1,v2) 
    out = 0 
    for k in range(len(v1)): 
     out += v1[k] * v2[k] 
    return out 

def d1(v1,v2): 
    """                          
    d1 uses an imap (from itertools)                   
    """ 
    check(v1,v2) 
    return sum(imap(mul,v1,v2)) 

def d2(v1,v2): 
    """                          
    d2 uses a conventional map                    
    """ 
    check(v1,v2) 
    return sum(map(mul,v1,v2)) 

def d3(v1,v2): 
    """                          
    d3 uses a starmap (itertools) to apply the mul operator on an izipped (v1,v2)       
    """ 
    check(v1,v2) 
    return sum(starmap(mul,izip(v1,v2))) 

# generate the test vectors                     
v1 = v() 
v2 = v() 

if __name__ == '__main__': 

    # Generate two test vectors of dimension N                

    t0 = timeit.Timer("d0(v1,v2)","from dot_product import d0,v1,v2") 
    t1 = timeit.Timer("d1(v1,v2)","from dot_product import d1,v1,v2") 
    t2 = timeit.Timer("d2(v1,v2)","from dot_product import d2,v1,v2") 
    t3 = timeit.Timer("d3(v1,v2)","from dot_product import d3,v1,v2") 

    print "d0 elapsed: ", t0.timeit() 
    print "d1 elapsed: ", t1.timeit() 
    print "d2 elapsed: ", t2.timeit() 
    print "d3 elapsed: ", t3.timeit() 

Lưu ý rằng tên của tệp phải là dot_product.py để chạy tập lệnh; Tôi đã sử dụng Python 2.5.1 trên Mac OS X Phiên bản 10.5.8.

EDIT:

Tôi chạy kịch bản cho N = 1000 và đây là những kết quả (tính bằng giây, cho một triệu chạy):

d0: 205.35457 
d1: 208.13006 
d2: 230.07463 
d3: 155.29670 

Tôi đoán nó là an toàn để giả định rằng, thực sự , tùy chọn ba là nhanh nhất và lựa chọn hai chậm nhất (trong số bốn trình bày).

+0

@Arrieta: Bạn có thể xóa yêu cầu rằng tệp được gọi là dot_product.py bằng cách thay thế 'từ dot_product' bằng 'từ __main__'. – unutbu

+0

@unutbu: Chắc chắn, tôi chỉ nghĩ đơn giản hơn là lưu tệp với tên đó để chạy nhanh hơn là thay đổi tập lệnh. Cảm ơn bạn. – Escualo

+1

Kết quả của tôi là: d0 đã trôi qua: 13.4328830242 d1 đã trôi qua: 9.52215504646 d2 đã trôi qua: 10.1050257683 d3 trôi qua: 9.16764998436 Hãy chắc chắn kiểm tra xem sự khác biệt giữa d1 và d3 có ý nghĩa thống kê hay không. – liori

Trả lời

8

Just for fun Tôi đã viết một "d4" trong đó sử dụng NumPy:

from numpy import dot 
def d4(v1, v2): 
    check(v1, v2) 
    return dot(v1, v2) 

kết quả của tôi (Python 2.5.1, XP Pro sp3, 2GHz Core2 Duo T7200):

d0 elapsed: 12.1977242918 
d1 elapsed: 13.885232341 
d2 elapsed: 13.7929552499 
d3 elapsed: 11.0952246724 

d4 đã trôi qua: 56.3278584289 # go gumpy!

Và, cho dù niềm vui hơn, tôi bật Psyco:

d0 elapsed: 0.965477735299 
d1 elapsed: 12.5354792299 
d2 elapsed: 12.9748163524 
d3 elapsed: 9.78255448667 

d4 trôi qua: 54,4599059378

Trên cơ sở đó, tôi tuyên bố D0 người chiến thắng :)


Cập nhật

@kaiser.se: Tôi có lẽ đã nói rằng tôi đã chuyển đổi tất cả mọi thứ để NumPy mảng đầu tiên:

from numpy import array 
v3 = [array(vec) for vec in v1] 
v4 = [array(vec) for vec in v2] 

# then 
t4 = timeit.Timer("d4(v3,v4)","from dot_product import d4,v3,v4") 

Và tôi bao gồm check(v1, v2) vì nó bao gồm trong các bài kiểm tra khác. Để lại nó sẽ cung cấp cho một lợi thế không lành mạnh (mặc dù nó có vẻ như nó có thể sử dụng một). Việc chuyển đổi mảng cạo ra khoảng một giây (ít hơn nhiều so với tôi nghĩ rằng nó sẽ).

Tất cả các thử nghiệm của tôi được chạy với N = 50.

@nikow: Tôi đang sử dụng gumpy 1.0.4, chắc chắn là cũ, chắc chắn rằng họ đã cải thiện hiệu suất trong năm qua và một nửa kể từ khi tôi cài đặt nó.


Update # 2

@ kaiser.se Wow, bạn là hoàn toàn đúng đắn. Tôi phải đã nghĩ rằng đây là danh sách các danh sách hoặc một cái gì đó (tôi thực sự không có ý tưởng những gì tôi đã suy nghĩ ... 1 cho lập trình cặp).

Làm thế nào để nhìn này:

v3 = array(v1) 
v4 = array(v2) 

kết quả mới:

d4 elapsed: 3.22535741274 

Với Psyco:

d4 elapsed: 2.09182619579 

d0 vẫn thắng với Psyco, nhưng NumPy có lẽ là tổng thể tốt hơn, đặc biệt với các tập dữ liệu lớn hơn.

Hôm qua tôi hơi bực mình vì kết quả uể oải chậm chạp của tôi, vì có lẽ được sử dụng cho nhiều tính toán và đã tối ưu hóa rất nhiều. Rõ ràng là mặc dù, không bận tâm, đủ để kiểm tra kết quả của tôi :)

+0

Phát hiện tuyệt vời, Seth! Đầu tiên, thật đáng kinh ngạc là Numpy quá chậm! Tôi hy vọng nó sẽ nhanh hơn nhiều. Ngoài ra, tôi không có manh mối về Psyco (và tôi coi bản thân mình là một kẻ lừa đảo Python!) - cảm ơn vì đã chỉ ra nó, tôi chắc chắn sẽ kiểm tra nó. Cuối cùng, thật thú vị khi thấy rằng, về cơ bản, điều Psyco đã làm cho mã C tối ưu hóa thuần túy cho d0 và không biết cách tối ưu hóa d3. Tôi đoán thông điệp là, nếu bạn muốn sử dụng Psyco, bạn nên đặt ra thuật toán để nó có thể được tối ưu hóa (trái ngược với "ẩn" logic của nó đằng sau các cấu trúc Python). Một lần nữa, những phát hiện tuyệt vời! – Escualo

+0

Có thể đã xảy ra sự cố với bản cài đặt khó xử của bạn. Trên máy của tôi numpy là nhanh hơn nhiều so với các tùy chọn khác (tôi đã không cố gắng psyco). Và N = 50 là một chút nhỏ cho numpy để hiển thị sức mạnh của nó. – nikow

+5

bạn đang làm sai. làm cho mảng numpy một lần (thay vì đi qua danh sách sẽ được chuyển đổi bởi numpy * mỗi lần *), và numpy sẽ nhanh hơn nhiều. cũng thả tờ séc. – u0b34a0f6ae

4

Tôi không biết về nhanh hơn, nhưng tôi muốn đề nghị:

sum(i*j for i, j in zip(v1, v2)) 

nó dễ dàng hơn để đọc và không đòi hỏi thậm chí module tiêu chuẩn thư viện.

+0

@SilentGhost: cách tiếp cận của bạn mất nhiều thời gian hơn. Đối với N = 10, nó mất 18.0258 giây (một triệu lần chạy). Những gì tôi đang tìm kiếm là tốc độ; thực sự dễ đọc là một vấn đề, vì sản phẩm dot được gọi từ một hàm (udotv = dot (u, v)), và tôi có thể nhận xét mã nhiều như tôi cần trong định nghĩa dấu chấm. Cách tiếp cận của bạn thực sự không thích hợp. – Escualo

+1

@SilentGhost, lưu trữ nhanh chóng: thay đổi zip thành itertools.izip giảm thời gian xuống 15.84879. Có lẽ đáng để biết? – Escualo

+3

nếu hiệu suất là rất lớn, hãy viết nó trong C – SilentGhost

3

Đây là một so sánh với numpy. Chúng tôi so sánh cách tiếp cận starmap nhanh với numpy.dot

Thứ nhất, lặp đi lặp lại trên danh sách Python bình thường:

$ python -mtimeit "import numpy as np; r = range(100)" "np.dot(r,r)" 
10 loops, best of 3: 316 usec per loop 

$ python -mtimeit "import operator; r = range(100); from itertools import izip, starmap" "sum(starmap(operator.mul, izip(r,r)))" 
10000 loops, best of 3: 81.5 usec per loop 

Sau đó NumPy ndarray:

$ python -mtimeit "import numpy as np; r = np.arange(100)" "np.dot(r,r)" 
10 loops, best of 3: 20.2 usec per loop 

$ python -mtimeit "import operator; import numpy as np; r = np.arange(100); from itertools import izip, starmap;" "sum(starmap(operator.mul, izip(r,r)))" 
10 loops, best of 3: 405 usec per loop 

Thấy vậy, có vẻ như NumPy trên mảng NumPy là nhanh nhất, tiếp theo là các cấu trúc hàm python làm việc với các danh sách.

0

Hãy đánh giá chức năng "d2a" này và so sánh nó với hàm "d3" của bạn.

from itertools import imap, starmap 
from operator import mul 

def d2a(v1,v2): 
    """ 
    d2a uses itertools.imap 
    """ 
    check(v1,v2) 
    return sum(imap(mul, v1, v2)) 

map (trên Python 2.x, đó là những gì tôi giả sử bạn sử dụng) không cần thiết tạo ra một danh sách giả trước khi tính toán.