文章目录

缓冲区与缓存(buffer与cache)

1.缓冲区buffer

缓冲区(buffer),它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。

缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

  • 高速设备与低速设备的不匹配,势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区。

缓存区的作用

  1. 可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率。例如:我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
  2. 可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费很多时间,因为开始读写与终止读写所需要的时间很长,如果将数据送往缓冲区,待缓冲区满后再进行传送会大大减少读写次数,这样就可以节省很多时间。例如:我们想将数据写入到磁盘中,不是立马将数据写到磁盘中,而是先输入缓冲区中,当缓冲区满了以后,再将数据写入到磁盘中,这样就可以减少磁盘的读写次数,不然磁盘很容易坏掉。

简单来说,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来存储数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

Python中的缓冲

  • StringIO和BytesIO就使用了缓冲技术

缓存区的类型

缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。

  1. 全缓冲
    在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
  2. 行缓冲
    在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
  3. 不带缓冲
    也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

2.缓存cache

缓存是现在系统中必不可少的模块,并且已经成为了高并发高性能架构的一个关键组件。这篇博客我们来分析一下使用缓存的正确姿势

  • 提升性能
    绝大多数情况下,select 是出现性能问题最大的地方。一方面,select 会有很多像 join、group、order、like 等这样丰富的语义,而这些语义是非常耗性能的;另一方面,大多 数应用都是读多写少,所以加剧了慢查询的问题。
    分布式系统中远程调用也会耗很多性能,因为有网络开销,会导致整体的响应时间下降。为了挽救这样的性能开销,在业务允许的情况(不需要太实时的数据)下,使用缓存是非常必要的事情。
  • 缓解数据库压力
    当用户请求增多时,数据库的压力将大大增加,通过缓存能够大大降低数据库的压力。

缓存的适用场景

  • 对于数据实时性要求不高
    对于一些经常访问但是很少改变的数据,读明显多于写,适用缓存就很有必要。比如一些网站配置项。
  • 对于性能要求高
    比如一些秒杀活动场景。

缓存的三种模式

  • Cache Aside 更新模式
  • Read/Write Through 更新模式
  • Write Behind Caching 更新模式
    通俗一点来讲就是,同时更新缓存和数据库(Cache Aside 更新模式);先更新缓存,缓存负责同步更新数据库(Read/Write Through 更新模式);先更新缓存,缓存定时异步更新数据库(Write Behind Caching 更新模式)。这三种模式各有优劣,可以根据业务场景选择使用。
  • 详细请参考​​javascript:void(0)​

Python中的缓存

  • functools.lru_cache(maxsize=128,typed=False) #cache缓存。为函数添加缓存机制。通过一个字典缓存被装饰函数的调用和返回值。
  1. Least-recently-used装饰器。lru,最近最少使用。cache缓存
  2. maxsize:如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂时,LRU功能执行得最好
  3. 如果typed设置为True,则不同类型的函数参数将单独缓存。例如f(3)和f(3.0)将被视为具有不同结果的不同调用
  4. 示例
import functools
import time

@functools.lru_cache()
def add(x,y):
time.sleep(1)
return x + y

print(add(4,5))
print(add(4.0,5))
print(add(4,6))
print("--------------")
print(add(4,5)) #会使用缓存,无需等待,直接出结果
print(add(4.0,5)) #会使用缓存,无序等待,直接出结果
print(add(4,6)) #会使用缓存,无序等待,直接出结果
print("-------------------")
print(add(4,y=6)) #无法使用缓存
print(add(x=4,y=6)) #无法使用缓存
print(add(y=6,x=4)) #无法使用缓存
  • lru_cache装饰器
  1. 斐波拉契数列递归方法的改造
import functools,datetime

def fib(n):
return 1 if n<3 else fib(n-1)+fib(n-2)

@functools.lru_cache(maxsize=None)
def fib2(n):
return 1 if n<3 else fib2(n-1)+fib2(n-2)

dt = datetime.datetime.now()
print([fib2(i+1) for i in range(35)])
dt1 = datetime.datetime.now()
print("(fib2)使用lru_cache缓存技术,用时:{}".format((dt1-dt).total_seconds()))
print([fib(i+1) for i in range(35)])
dt2 = datetime.datetime.now()
print("(fib)不使用lru_cache缓存技术,用时:{}".format((dt2-dt1).total_seconds()))
  • lru_cache装饰器应用
  1. 使用前提
  • 同样的函数参数一定得到同样的结果
  • 函数执行时间很长,且要多次执行
  1. 本质是函数调用函数=》返回值
  2. 缺点
  • 不支持缓存过期,key无法过期、失效
  • 不支持清除操作
  • 不支持分布式,是一个单机的缓存
  1. 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
  • 实现一个cache装饰器,实现可过期被清除的功能
  1. 简化设计,函数的形参定义不包含可变位置参数、可变关键字参数和keyword-only参数 可以不考虑缓存大小,也不用考虑缓存满了之后的换出问题
import inspect,functools,datetime

def xdd_time(fn):
"""
时间统计,统计函数执行时间
:param fn: 需要装饰的函数
:return: 包装后的fn
"""
@functools.wraps(fn)
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
req = fn(*args,**kwargs)
timeout = datetime.datetime.now() - start
print("{}方法耗时:{},args={},kwargs={},\n执行结果:req={}".format(fn.__name__,timeout.total_seconds(),args,kwargs,req))
return req
return wrapper

def xdd_cache(duration = 5):
def _xdd_cache(fn):
"""
为函数生成缓存机制,具有有效时长功能
:param fn:
:param duration:缓存时长,单位秒,如果为None表示不计算时长,默认全部缓存
:return:
"""
xdd_dict = {} #定义一个字典,用来存参数与对应的计算结果
sig = inspect.signature(fn) #对函数进行签名

@functools.wraps(fn)
def wrapper(*args,**kwargs):
paramestr = getparamestr(args,kwargs) #根据参数获取字符串
req = xdd_dict.get(paramestr,None)
if req is None:
req = fn(*args,**kwargs),datetime.datetime.now()
xdd_dict[paramestr] = req
elif not duration is None and (datetime.datetime.now() - req[1]).total_seconds() > duration: #超时
print("缓存超时")
req = fn(*args, **kwargs),datetime.datetime.now()
xdd_dict[paramestr] = req
return req[0]

def getparamestr(args,kwargs)->str:
"""
格式化参数,将参数格式化为指定格式的字符串
:param args:
:param kwargs:
:return:
"""
parames = sig.parameters
pstr = ()
isargs = True #标记是否还有位置参数
kwdict = {}
for k,(name,v) in enumerate(parames.items()):
v:inspect.Parameter = v
if name == "args": #位置参数已经全部匹配完成
isargs = False
pstr += ("args",tuple(args[k+1:])) #记录剩下的位置参数
continue
if name == "kwargs": #关键字位置参数已经匹配完
pstr += ("kwargs",tuple((k,kwargs[k]) for k in kwargs.keys()-kwdict.keys()))
break
if isargs: # 位置参数还没匹配完
if k<len(args):
pstr += (name, args[k])
else: #实际传参中位置参数已经传完
kwval = kwargs.get(name,v.default)
kwdict[name] = kwval # 记录已经出现的位置参数
pstr += (name,kwval)
else:
kwval = kwargs.get(name, v.default)
kwdict[name] = kwval #记录已经出现的位置参数
pstr += (name,kwval)
# print(k,name,v.name,v.annotation,v.default)
return pstr
return wrapper
return _xdd_cache

# @gdy_cache
# def add(x,y:int=5,*args,z=4,b:int,c=6,**kwargs):
# import time
# time.sleep(3)
# return x+y
@xdd_time
@xdd_cache()
def add(x=4,y=5):
import time
time.sleep(3)
return x+y
print("开始计算")
print(1,add(4,5))
import time
time.sleep(5)
print(2,add(4))
# print(3,add(y=5))
# print(4,add(x=4,y=5))
# print(5,add(y=5,x=4))
# print(6,add())

缓冲区与缓存(buffer与cache)_buffer