मैं अजगर में एक रेडिस कैश बनाना चाहता था, और किसी भी आत्म सम्मान वैज्ञानिक के रूप में मैंने प्रदर्शन का परीक्षण करने के लिए एक बेंच मार्क बनाया था।कैशिंग अनुप्रयोग में रेडिस बनाम डिस्क का प्रदर्शन
दिलचस्प बात यह है कि रेडिस इतनी अच्छी तरह से किराया नहीं था। या तो पायथन कुछ जादू कर रहा है (फ़ाइल संग्रहित) या रेडिस का मेरा संस्करण बेहद धीमा है।
मुझे नहीं पता कि यह मेरे कोड की संरचना के तरीके के कारण है या क्या, लेकिन मैं लालसा से बेहतर करने की अपेक्षा कर रहा था।
रेडिस कैश बनाने के लिए, मैंने 5 मिनट की समाप्ति के साथ फ़ाइल नाम से प्राप्त कुंजी पर अपना बाइनरी डेटा (इस मामले में, एक HTML पृष्ठ) सेट किया है।
सभी मामलों में, फ़ाइल हैंडलिंग f.read() के साथ किया जाता है (यह f.readlines() से ~ 3x तेज है, और मुझे बाइनरी ब्लॉब की आवश्यकता है)।
क्या मेरी तुलना में कुछ याद आ रही है, या क्या रेडिस वास्तव में डिस्क के लिए कोई मिलान नहीं है? क्या पाइथन कहीं फाइल को कैश कर रहा है, और हर बार इसे पुनः प्राप्त कर रहा है? रेडिस तक पहुंच से यह इतना तेज़ क्यों है?
मैं रेडिस 2.8, पायथन 2.7, और रेडिस-पीई का उपयोग कर रहा हूं, सब 64 बिट उबंटू सिस्टम पर।
मुझे नहीं लगता कि पाइथन विशेष रूप से जादुई कुछ भी कर रहा है, क्योंकि मैंने एक ऐसा फ़ंक्शन बनाया है जो फ़ाइल डेटा को एक पायथन ऑब्जेक्ट में संग्रहीत करता है और इसे हमेशा के लिए उत्पन्न करता है।
मैं चार फ़ंक्शन को कॉल मैं वर्गीकृत किया है:
फ़ाइल पढ़ी जा X बार
एक समारोह है कि अगर redis वस्तु स्मृति में अब भी है देखते हैं, इसे लोड, या नई फ़ाइल कैश (एकल के लिए कहा जाता है और कई redis उदाहरण)।
एक ऐसा फ़ंक्शन जो जेनरेटर बनाता है जो रेडिस डेटाबेस (रेडिस के एकल और बहु उदाहरणों के साथ) से परिणाम उत्पन्न करता है।
और अंत में, फ़ाइल को स्मृति में संग्रहीत करना और इसे हमेशा के लिए उपज देना।
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)
और अब परिणाम:
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)
परिणाम है कि एक) जनरेटर में दिलचस्प तेजी से काम करता है हर बार, ख) redis डिस्क से पढ़ने की तुलना में धीमी है बुला से कर रहे हैं, और ग) अजगर वस्तुओं से पढ़ना हास्यास्पद रूप से तेज़ है।
डिस्क से रीडिस में एक मेमोरी फ़ाइल से पढ़ने की तुलना में डिस्क से क्यों पढ़ना इतना तेज़ होगा?
संपादित करें: कुछ और जानकारी और परीक्षण।
मैं
data = r.get(fpKey)
if data:
return r.get(fpKey)
को समारोह की जगह परिणाम
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)
प्रत्येक समारोह कॉल पर एक नया redis उदाहरण बनाना वास्तव में एक उल्लेखनीय पढ़ने की गति पर कोई प्रभाव नहीं है से बहुत अलग नहीं है, परीक्षण से परीक्षण में परिवर्तनशीलता लाभ से बड़ी है।
श्रीपति कृष्णन ने यादृच्छिक फ़ाइल पढ़ने को लागू करने का सुझाव दिया। यह वह जगह है जहां कैशिंग वास्तव में मदद करने लगती है, क्योंकि हम इन परिणामों से देख सकते हैं।
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)
फ़ाइल में परिवर्तनशीलता का एक विशाल राशि पढ़ने इतना प्रतिशत अंतर speedup का एक अच्छा सूचक नहीं है।
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)
मैंने यादृच्छिक रूप से कार्यों के माध्यम से प्रत्येक पास पर एक नई फ़ाइल का चयन करने के लिए random.choice (fileList) का उपयोग किया।
पूर्ण सार यहाँ है अगर किसी को भी इसे आज़माने के लिए चाहते हैं - https://gist.github.com/3885957
संपादित करें संपादित करें: एहसास नहीं था कि मैं जनरेटर के लिए एक एकल फाइल को बुला रहा था (हालांकि समारोह कॉल और जनरेटर के प्रदर्शन बहुत समान था)। जेनरेटर से अलग-अलग फाइलों का नतीजा यहां दिया गया है।
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)
मुझे नहीं लगता कि आप प्रत्येक फ़ंक्शन कॉल पर एक नया रेडिस उदाहरण कहां बना रहे थे। क्या यह सिर्फ डिफ़ॉल्ट तर्क बात थी? – jdi
हां, यदि आप रेडिस इंस्टेंस पास नहीं करते हैं तो फ़ंक्शन कॉल एक नया एक def cache_or_get (fp, expiry = 300, r = redis.Redis (db = 5)): – MercuryRising
वास्तव में सत्य नहीं है। उन डिफ़ॉल्ट तर्कों का केवल तभी मूल्यांकन किया जाता है जब स्क्रिप्ट लोड हो जाती है, और फ़ंक्शन परिभाषा के साथ सहेजी जाती है। हर बार जब आप इसे कॉल करते हैं तो उनका मूल्यांकन नहीं किया जाता है। यह समझाएगा कि आपको किसी को गुजरने या इसे डिफ़ॉल्ट का उपयोग करने के बीच कोई अंतर क्यों नहीं दिख रहा था। असल में आप जो कर रहे थे वह प्रत्येक फंक्शन डीफ़ के लिए एक बना रहा है, साथ ही प्रत्येक बार जब आप इसे पास कर रहे थे। 2 अप्रयुक्त कनेक्शन – jdi