Tăng tốc độ tính toán là mục tiêu mà ai cũng muốn đạt được. Điều gì sẽ xảy ra nếu bạn có một tập lệnh có thể chạy nhanh hơn mười lần so với thời gian chạy hiện tại của nó? . Chúng ta sẽ nói về đa xử lý là gì, ưu điểm của nó và cách cải thiện thời gian chạy chương trình Python của bạn bằng cách sử dụng lập trình song song
Được rồi, vậy chúng ta hãy đi
Giới thiệu về song song
Trước khi đi sâu vào mã Python, chúng ta phải nói về điện toán song song, đây là một khái niệm quan trọng trong khoa học máy tính
Thông thường, khi bạn chạy tập lệnh Python, mã của bạn tại một thời điểm nào đó sẽ trở thành một quy trình và quy trình này sẽ chạy trên một lõi CPU của bạn. Nhưng các máy tính hiện đại có nhiều hơn một lõi, vậy nếu bạn có thể sử dụng nhiều lõi hơn cho các tính toán của mình thì sao?
Bây giờ chúng ta hãy coi đây là một nguyên tắc chung, nhưng sau này, trong bài viết này, chúng ta sẽ thấy rằng điều này không đúng một cách phổ biến
Không đi vào quá nhiều chi tiết, ý tưởng đằng sau tính song song là viết mã của bạn theo cách nó có thể sử dụng nhiều lõi của CPU
Để làm cho mọi thứ dễ dàng hơn, hãy xem một ví dụ
Điện toán song song và nối tiếp
Hãy tưởng tượng bạn có một vấn đề lớn cần giải quyết và bạn chỉ có một mình. Bạn cần tính căn bậc hai của tám số khác nhau. Bạn làm nghề gì? . Bạn bắt đầu với số đầu tiên và bạn tính kết quả. Sau đó, bạn tiếp tục với những người khác
Điều gì sẽ xảy ra nếu bạn có ba người bạn giỏi toán sẵn sàng giúp đỡ bạn? . Điều này có nghĩa là vấn đề của bạn sẽ được giải quyết nhanh hơn
Được rồi, như vậy tất cả rõ ràng? . Trong ví dụ đầu tiên, toàn bộ nhiệm vụ được bạn giải quyết tuần tự. Điều này được gọi là tính toán nối tiếp. Trong ví dụ thứ hai, vì bạn đang làm việc với tổng cộng bốn lõi, nên bạn đang sử dụng tính toán song song. Tính toán song song liên quan đến việc sử dụng các quy trình hoặc quy trình song song được phân chia giữa nhiều lõi trong bộ xử lý
Mô hình lập trình song song
Chúng tôi đã thiết lập lập trình song song là gì, nhưng chúng tôi sử dụng nó như thế nào? . Có một số câu hỏi mà bạn nên xem xét trước khi tiếp cận song song hóa. Ví dụ: có bất kỳ tối ưu hóa nào khác có thể tăng tốc độ tính toán của chúng tôi không?
Hiện tại, hãy chấp nhận rằng song song hóa là giải pháp tốt nhất cho bạn. Có ba mô hình chủ yếu trong tính toán song song
- hoàn toàn song song. Các tác vụ có thể được chạy độc lập và chúng không cần giao tiếp với nhau
- Bộ nhớ chia sẻ song song. Các quy trình [hoặc luồng] cần giao tiếp, vì vậy chúng chia sẻ một không gian địa chỉ chung
- Thông qua. Các tiến trình cần chia sẻ thông điệp khi cần
Trong bài viết này, chúng tôi sẽ minh họa mô hình đầu tiên, cũng là mô hình đơn giản nhất
Đa xử lý Python. Xử lý song song dựa trên quy trình trong Python
Một cách để đạt được tính song song trong Python là sử dụng mô-đun đa xử lý. Mô-đun
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1 cho phép bạn tạo nhiều quy trình, mỗi quy trình có trình thông dịch Python riêng. Vì lý do này, đa xử lý Python thực hiện song song dựa trên quy trìnhBạn có thể đã nghe nói về các thư viện khác, chẳng hạn như
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
3, cũng được tích hợp sẵn trong Python, nhưng có những điểm khác biệt quan trọng giữa chúng. Mô-đun from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1 tạo quy trình mới, trong khi from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
3 tạo chủ đề mớiTrong phần tiếp theo, chúng ta sẽ xem xét những lợi ích của việc sử dụng đa xử lý
Lợi ích của việc sử dụng đa xử lý
Dưới đây là một vài lợi ích của đa xử lý
- sử dụng CPU tốt hơn khi xử lý các tác vụ sử dụng nhiều CPU
- kiểm soát nhiều hơn đối với một đứa trẻ so với chủ đề
- dễ viết mã
Ưu điểm đầu tiên liên quan đến hiệu suất. Vì đa xử lý tạo ra các quy trình mới, bạn có thể sử dụng hiệu quả hơn sức mạnh tính toán của CPU bằng cách phân chia nhiệm vụ của mình cho các lõi khác. Hầu hết các bộ xử lý hiện nay đều là bộ xử lý đa lõi và nếu bạn tối ưu hóa mã của mình, bạn có thể tiết kiệm thời gian bằng cách giải các phép tính song song
Ưu điểm thứ hai xem xét một giải pháp thay thế cho đa xử lý, đó là đa luồng. Tuy nhiên, các luồng không phải là các quy trình và điều này có hậu quả của nó. Nếu bạn tạo một luồng, sẽ rất nguy hiểm khi tắt nó hoặc thậm chí làm gián đoạn nó như bạn làm với một quy trình bình thường. Vì việc so sánh giữa đa xử lý và đa luồng không nằm trong phạm vi của bài viết này, tôi khuyến khích bạn đọc thêm về nó
Ưu điểm thứ ba của đa xử lý là nó khá dễ triển khai, do tác vụ bạn đang cố xử lý phù hợp với lập trình song song
Bắt đầu với Đa xử lý Python
Cuối cùng thì chúng ta cũng đã sẵn sàng để viết một số mã Python
Chúng tôi sẽ bắt đầu với một ví dụ rất cơ bản và chúng tôi sẽ sử dụng nó để minh họa các khía cạnh cốt lõi của đa xử lý Python. Trong ví dụ này, chúng ta sẽ có hai quy trình
- Quá trình
0. Chỉ có một tiến trình cha, có thể có nhiều tiến trình confrom multiprocessing import Pool import time import math N = 5000000 def cube[x]: return math.sqrt[x] if __name__ == "__main__": with Pool[] as pool: result = pool.map[cube, range[10,N]] print["Program finished!"]
- Quá trình
1. Điều này được sinh ra bởi cha mẹ. Mỗi đứa trẻ cũng có thể có con mớifrom multiprocessing import Pool import time import math N = 5000000 def cube[x]: return math.sqrt[x] if __name__ == "__main__": with Pool[] as pool: result = pool.map[cube, range[10,N]] print["Program finished!"]
Chúng tôi sẽ sử dụng quy trình
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1 để thực hiện một chức năng nhất định. Bằng cách này, from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
0 có thể tiếp tục thực hiệnMột ví dụ đa xử lý Python đơn giản
Đây là mã chúng tôi sẽ sử dụng cho ví dụ này
from multiprocessing import Process
def bubble_sort[array]:
check = True
while check == True:
check = False
for i in range[0, len[array]-1]:
if array[i] > array[i+1]:
check = True
temp = array[i]
array[i] = array[i+1]
array[i+1] = temp
print["Array sorted: ", array]
if __name__ == '__main__':
p = Process[target=bubble_sort, args=[[1,9,4,5,2,6,8,4],]]
p.start[]
p.join[]
Trong đoạn mã này, chúng ta đã định nghĩa một hàm có tên là
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
4. Chức năng này là một triển khai thực sự ngây thơ của thuật toán sắp xếp Bubble Sort. Nếu bạn không biết nó là gì, đừng lo lắng, vì nó không quan trọng lắm. Điều quan trọng cần biết là đó là một chức năng thực hiện một số công việcLớp quy trình
Từ
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1, chúng tôi nhập lớp from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
6. Lớp này đại diện cho một hoạt động sẽ được chạy trong một quy trình riêng biệt. Thật vậy, bạn có thể thấy rằng chúng tôi đã thông qua một số đối số
7, nghĩa là quy trình mới của chúng ta sẽ chạy hàmfrom multiprocessing import Pool import time import math N = 5000000 def cube[x]: return math.sqrt[x] if __name__ == "__main__": with Pool[] as pool: result = pool.map[cube, range[10,N]] print["Program finished!"]
8from multiprocessing import Pool import time import math N = 5000000 def cube[x]: return math.sqrt[x] if __name__ == "__main__": with Pool[] as pool: result = pool.map[cube, range[10,N]] print["Program finished!"]
9, là mảng được truyền làm đối số cho hàm đíchfrom multiprocessing import Pool import time import math N = 5000000 def cube[x]: return math.sqrt[x] if __name__ == "__main__": with Pool[] as pool: result = pool.map[cube, range[10,N]] print["Program finished!"]
Khi chúng tôi đã tạo một thể hiện cho lớp Quy trình, chúng tôi chỉ cần bắt đầu quy trình. Điều này được thực hiện bằng cách viết
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
00. Tại thời điểm này, quá trình bắt đầuTrước khi thoát, chúng ta cần đợi tiến trình con hoàn thành việc tính toán của nó. Phương thức
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
01 chờ quá trình kết thúcTrong ví dụ này, chúng tôi chỉ tạo một tiến trình con. Như bạn có thể đoán, chúng ta có thể tạo nhiều tiến trình con hơn bằng cách tạo nhiều thể hiện hơn trong lớp
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
6lớp bi-a
Điều gì sẽ xảy ra nếu chúng ta cần tạo nhiều quy trình để xử lý các tác vụ sử dụng nhiều CPU hơn?
Lớp
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
03 cho phép bạn tạo một nhóm quy trình worker và trong ví dụ sau, chúng ta sẽ xem cách chúng ta có thể sử dụng nó. Đây là ví dụ mới của chúng tôifrom multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
Trong đoạn mã này, chúng ta có một hàm
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
05 chỉ cần lấy một số nguyên và trả về căn bậc hai của nó. Dễ dàng, phải không?Sau đó, chúng tôi tạo một thể hiện của lớp
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
03 mà không chỉ định bất kỳ thuộc tính nào. Lớp nhóm tạo theo mặc định một quy trình cho mỗi lõi CPU. Tiếp theo, chúng tôi chạy phương thức from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
07 với một vài đối sốPhương thức
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
07 áp dụng hàm from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
09 cho mọi phần tử của iterable mà chúng tôi cung cấp — trong trường hợp này, là danh sách mọi số từ from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
10 đến from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
11Ưu điểm rất lớn của điều này là các tính toán trong danh sách được thực hiện song song
Tận dụng tốt nhất Đa xử lý Python
Tạo nhiều quy trình và thực hiện tính toán song song không nhất thiết phải hiệu quả hơn tính toán nối tiếp. Đối với các tác vụ sử dụng nhiều CPU thấp, tính toán nối tiếp nhanh hơn tính toán song song. Vì lý do này, điều quan trọng là phải hiểu khi nào bạn nên sử dụng đa xử lý — điều này phụ thuộc vào các tác vụ bạn đang thực hiện
Để thuyết phục bạn về điều này, hãy xem một ví dụ đơn giản
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
0Đoạn mã này dựa trên ví dụ trước. Chúng ta đang giải cùng một bài toán, đó là tính căn bậc hai của số
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
11, nhưng theo hai cách. Cái đầu tiên liên quan đến việc sử dụng đa xử lý Python, trong khi cái thứ hai thì không. Chúng tôi đang sử dụng phương pháp from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
13 từ thư viện from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
14 để đo hiệu suất thời gianTrên máy tính xách tay của tôi, tôi nhận được kết quả này
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1Như bạn có thể thấy, có hơn một giây khác biệt. Vì vậy, trong trường hợp này, đa xử lý sẽ tốt hơn
Hãy thay đổi điều gì đó trong mã, chẳng hạn như giá trị của
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
11. Hãy hạ nó xuống from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
16 và xem điều gì sẽ xảy raĐây là những gì tôi nhận được bây giờ
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
0Chuyện gì đã xảy ra thế? . Tại sao?
Chi phí hoạt động được đưa ra bằng cách phân chia các tính toán giữa các quy trình là quá nhiều so với nhiệm vụ được giải quyết. Bạn có thể thấy có bao nhiêu sự khác biệt về thời gian biểu diễn
Phần kết luận
Trong bài viết này, chúng ta đã nói về việc tối ưu hóa hiệu suất của mã Python bằng cách sử dụng đa xử lý Python
Đầu tiên, chúng tôi đã giới thiệu ngắn gọn điện toán song song là gì và các mô hình chính để sử dụng nó. Sau đó, chúng tôi bắt đầu nói về đa xử lý và những ưu điểm của nó. Cuối cùng, chúng tôi thấy rằng việc tính toán song song không phải lúc nào cũng là lựa chọn tốt nhất và mô-đun
from multiprocessing import Pool
import time
import math
N = 5000000
def cube[x]:
return math.sqrt[x]
if __name__ == "__main__":
with Pool[] as pool:
result = pool.map[cube, range[10,N]]
print["Program finished!"]
1 nên được sử dụng để thực hiện song song các tác vụ liên quan đến CPU. Như mọi khi, vấn đề là xem xét vấn đề cụ thể mà bạn đang gặp phải và đánh giá ưu và nhược điểm của các giải pháp khác nhauTôi hy vọng bạn thấy việc tìm hiểu về đa xử lý Python hữu ích như tôi đã làm
Chia sẻ bài viết này
Lorenzo Bonanella
Tôi là một sinh viên khoa học máy tính thích đặt câu hỏi và học hỏi những điều mới. Khách du lịch, nhạc sĩ và nhà văn thỉnh thoảng. Kiểm tra tôi trên trang web của tôi