Tôi muốn tạo bộ nhớ cache redis trong python, và như bất kỳ nhà khoa học tự tôn trọng nào tôi đã đánh dấu băng ghế dự bị để kiểm tra hiệu suất.Hiệu suất của Redis vs Disk trong ứng dụng bộ nhớ đệm
Điều thú vị là redis không hoạt động tốt. Hoặc Python đang làm điều gì đó ma thuật (lưu trữ các tập tin) hoặc phiên bản của tôi của redis là stupendously chậm.
Tôi không biết nếu điều này là do cách mã của tôi được cấu trúc, hoặc những gì, nhưng tôi đã mong đợi làm lại để làm tốt hơn so với nó đã làm.
Để tạo bộ nhớ cache redis, tôi đặt dữ liệu nhị phân của mình (trong trường hợp này là trang HTML) thành khóa được lấy từ tên tệp có thời hạn là 5 phút.
Trong mọi trường hợp, xử lý tệp được thực hiện với f.read() (đây là ~ 3x nhanh hơn f.readlines() và tôi cần blob nhị phân).
Có điều gì tôi thiếu trong so sánh của tôi hay Redis thực sự không phù hợp với đĩa? Python có lưu vào bộ nhớ đệm một nơi nào đó và xử lý nó mỗi lần không? Tại sao điều này nhanh hơn rất nhiều so với truy cập vào redis?
Tôi đang sử dụng redis 2.8, python 2.7 và redis-py, tất cả đều trên hệ thống Ubuntu 64 bit.
Tôi không nghĩ rằng Python đang làm bất cứ điều gì đặc biệt huyền diệu, như tôi đã thực hiện một chức năng lưu trữ dữ liệu tập tin trong một đối tượng python và mang lại nó mãi mãi.
Tôi có bốn chức năng cuộc gọi mà tôi theo nhóm:
Đọc file X lần
Một chức năng đó được gọi là để xem nếu đối tượng redis vẫn còn trong bộ nhớ, tải nó, hoặc bộ nhớ cache file mới (đơn và nhiều trường hợp redis).
Một chức năng tạo trình tạo ra kết quả từ cơ sở dữ liệu redis (với một lần và nhiều lần redis).
và cuối cùng, lưu trữ tệp trong bộ nhớ và giữ nó vĩnh viễn.
import redis
import time
def load_file(fp, fpKey, r, expiry):
with open(fp, "rb") as f:
data = f.read()
p = r.pipeline()
p.set(fpKey, data)
p.expire(fpKey, expiry)
p.execute()
return data
def cache_or_get_gen(fp, expiry=300, r=redis.Redis(db=5)):
fpKey = "cached:"+fp
while True:
yield load_file(fp, fpKey, r, expiry)
t = time.time()
while time.time() - t - expiry < 0:
yield r.get(fpKey)
def cache_or_get(fp, expiry=300, r=redis.Redis(db=5)):
fpKey = "cached:"+fp
if r.exists(fpKey):
return r.get(fpKey)
else:
with open(fp, "rb") as f:
data = f.read()
p = r.pipeline()
p.set(fpKey, data)
p.expire(fpKey, expiry)
p.execute()
return data
def mem_cache(fp):
with open(fp, "rb") as f:
data = f.readlines()
while True:
yield data
def stressTest(fp, trials = 10000):
# Read the file x number of times
a = time.time()
for x in range(trials):
with open(fp, "rb") as f:
data = f.read()
b = time.time()
readAvg = trials/(b-a)
# Generator version
# Read the file, cache it, read it with a new instance each time
a = time.time()
gen = cache_or_get_gen(fp)
for x in range(trials):
data = next(gen)
b = time.time()
cachedAvgGen = trials/(b-a)
# Read file, cache it, pass in redis instance each time
a = time.time()
r = redis.Redis(db=6)
gen = cache_or_get_gen(fp, r=r)
for x in range(trials):
data = next(gen)
b = time.time()
inCachedAvgGen = trials/(b-a)
# Non generator version
# Read the file, cache it, read it with a new instance each time
a = time.time()
for x in range(trials):
data = cache_or_get(fp)
b = time.time()
cachedAvg = trials/(b-a)
# Read file, cache it, pass in redis instance each time
a = time.time()
r = redis.Redis(db=6)
for x in range(trials):
data = cache_or_get(fp, r=r)
b = time.time()
inCachedAvg = trials/(b-a)
# Read file, cache it in python object
a = time.time()
for x in range(trials):
data = mem_cache(fp)
b = time.time()
memCachedAvg = trials/(b-a)
print "\n%s file reads: %.2f reads/second\n" %(trials, readAvg)
print "Yielding from generators for data:"
print "multi redis instance: %.2f reads/second (%.2f percent)" %(cachedAvgGen, (100*(cachedAvgGen-readAvg)/(readAvg)))
print "single redis instance: %.2f reads/second (%.2f percent)" %(inCachedAvgGen, (100*(inCachedAvgGen-readAvg)/(readAvg)))
print "Function calls to get data:"
print "multi redis instance: %.2f reads/second (%.2f percent)" %(cachedAvg, (100*(cachedAvg-readAvg)/(readAvg)))
print "single redis instance: %.2f reads/second (%.2f percent)" %(inCachedAvg, (100*(inCachedAvg-readAvg)/(readAvg)))
print "python cached object: %.2f reads/second (%.2f percent)" %(memCachedAvg, (100*(memCachedAvg-readAvg)/(readAvg)))
if __name__ == "__main__":
fileToRead = "templates/index.html"
stressTest(fileToRead)
Và bây giờ là kết quả:
10000 file reads: 30971.94 reads/second
Yielding from generators for data:
multi redis instance: 8489.28 reads/second (-72.59 percent)
single redis instance: 8801.73 reads/second (-71.58 percent)
Function calls to get data:
multi redis instance: 5396.81 reads/second (-82.58 percent)
single redis instance: 5419.19 reads/second (-82.50 percent)
python cached object: 1522765.03 reads/second (4816.60 percent)
Kết quả là thú vị ở chỗ a) máy phát điện là nhanh hơn so với gọi chức năng mỗi lần, b) redis là chậm hơn so với đọc từ đĩa, và c) đọc từ các đối tượng python là ridiculously nhanh.
Tại sao đọc từ đĩa nhanh hơn rất nhiều so với việc đọc từ tệp trong bộ nhớ từ redis?
EDIT: Một số thông tin và thử nghiệm khác.
Tôi thay thế các chức năng để
data = r.get(fpKey)
if data:
return r.get(fpKey)
Các kết quả không khác biệt nhiều so
if r.exists(fpKey):
data = r.get(fpKey)
Function calls to get data using r.exists as test
multi redis instance: 5320.51 reads/second (-82.34 percent)
single redis instance: 5308.33 reads/second (-82.38 percent)
python cached object: 1494123.68 reads/second (5348.17 percent)
Function calls to get data using if data as test
multi redis instance: 8540.91 reads/second (-71.25 percent)
single redis instance: 7888.24 reads/second (-73.45 percent)
python cached object: 1520226.17 reads/second (5132.01 percent)
Tạo một redis dụ mới về mỗi cuộc gọi chức năng thực sự không có một đáng chú ý ảnh hưởng đến tốc độ đọc, sự thay đổi từ thử nghiệm đến kiểm tra lớn hơn mức tăng.
Sripathi Krishnan đề xuất triển khai đọc tệp ngẫu nhiên. Đây là nơi bộ nhớ đệm bắt đầu thực sự hữu ích, như chúng ta có thể thấy từ những kết quả này.
Total number of files: 700
10000 file reads: 274.28 reads/second
Yielding from generators for data:
multi redis instance: 15393.30 reads/second (5512.32 percent)
single redis instance: 13228.62 reads/second (4723.09 percent)
Function calls to get data:
multi redis instance: 11213.54 reads/second (3988.40 percent)
single redis instance: 14420.15 reads/second (5157.52 percent)
python cached object: 607649.98 reads/second (221446.26 percent)
Có một số lượng lớn thay đổi trong tệp để chênh lệch phần trăm không phải là chỉ báo tốt về tăng tốc.
Total number of files: 700
40000 file reads: 1168.23 reads/second
Yielding from generators for data:
multi redis instance: 14900.80 reads/second (1175.50 percent)
single redis instance: 14318.28 reads/second (1125.64 percent)
Function calls to get data:
multi redis instance: 13563.36 reads/second (1061.02 percent)
single redis instance: 13486.05 reads/second (1054.40 percent)
python cached object: 587785.35 reads/second (50214.25 percent)
Tôi đã sử dụng random.choice (tệpList) để chọn ngẫu nhiên một tệp mới trên mỗi lần truyền qua các hàm.
Các ý chính đầy đủ là ở đây nếu có ai muốn thử nó ra - https://gist.github.com/3885957
Chỉnh sửa biên tập: đã không nhận ra rằng tôi đã gọi một tập tin duy nhất cho máy phát điện (mặc dù việc thực hiện các cuộc gọi chức năng và máy phát điện rất giống nhau). Đây là kết quả của các tập tin khác nhau từ máy phát điện.
Total number of files: 700
10000 file reads: 284.48 reads/second
Yielding from generators for data:
single redis instance: 11627.56 reads/second (3987.36 percent)
Function calls to get data:
single redis instance: 14615.83 reads/second (5037.81 percent)
python cached object: 580285.56 reads/second (203884.21 percent)
Tôi không thấy nơi bạn đang tạo một phiên bản redis mới trên mỗi cuộc gọi chức năng. Nó chỉ là điều đối số mặc định? – jdi
Có, nếu bạn không vượt qua một ví dụ redis cuộc gọi chức năng sẽ lấy một mới def cache_or_get (fp, expiry = 300, r = redis.Redis (db = 5)): – MercuryRising
Thats thực sự không đúng sự thật. Những đối số mặc định này chỉ được đánh giá một lần khi kịch bản được tải và được lưu với định nghĩa hàm. Chúng không được đánh giá mỗi khi bạn gọi nó. Điều đó sẽ giải thích lý do tại sao bạn không thấy bất kỳ sự khác biệt nào giữa việc truyền vào hoặc cho phép nó sử dụng giá trị mặc định. Trên thực tế những gì bạn đang làm là tạo ra một cho mỗi def chức năng, cộng với một cho mỗi khi bạn đã vượt qua nó trong. 2 kết nối không sử dụng – jdi