Data structures 字符串的循环缓冲区

Data structures 字符串的循环缓冲区,data-structures,rust,circular-buffer,Data Structures,Rust,Circular Buffer,我正在缓冲进程的stdout、stderr和stdin的最后X行。 我想保留最后的X行,并能够通过其id(行号)访问一行。 因此,如果我们存储100行并插入其中200行,您可以访问100-200行。 (实际上,我们希望存储约2000行。) 性能案例是插入。所以插入本身应该很快。检索偶尔会发生,但可能占用例的10%。 (我们大部分时间不会查看输出。) 旧方法,碎片化 我使用了一个包装ArrayDeque,然后在行计数上保留了一本书,但这意味着在上面的示例中我们使用的是[Vec;100]。字符串数组

我正在缓冲进程的stdout、stderr和stdin的最后X行。 我想保留最后的X行,并能够通过其id(行号)访问一行。 因此,如果我们存储100行并插入其中200行,您可以访问100-200行。 (实际上,我们希望存储约2000行。)

性能案例是插入。所以插入本身应该很快。检索偶尔会发生,但可能占用例的10%。 (我们大部分时间不会查看输出。)

旧方法,碎片化
我使用了一个包装
ArrayDeque
,然后在行计数上保留了一本书,但这意味着在上面的示例中我们使用的是
[Vec;100]
。字符串数组,因此是
Vec
数组

新方法,带开放性问题
我的*新想法是将数据存储在一个u8数组中,然后将book保留在数组中每个条目的起始位置和长度上。这里的问题是,我们需要簿记也成为某种环形缓冲区,并在数据数组必须包装时擦除旧条目。也许还有更好的方法来实现这一点?至少这充分利用了ringbuffer并防止了内存碎片

*还要感谢rust社区的sebk

当前简易方法

const MAX:usize=5;
pub结构LineRingBuffer{
柜台:期权,
资料来源:ArrayDeque,
min_line:使用,
}
impl LineRingBuffer{
pub fn new()->Self{
自我{
柜台:没有,
数据:ArrayDeque::new(),
最小线:0,
}
}
pub-fn-get选项{
如果让一些(最大)=自我计数器{
如果pos>=self.min\u行和pos=MAX{
self.min_line+=1;
}
}否则{
self.counter=Some(0);
}
}
}
对新想法草案提出质疑:

pub结构切片缓冲区{
柜台:期权,
min_line:使用,
数据框:,
索引:ArrayDeque,
}
结构条目{
开始:使用,
长度:usize,
}

不管出于什么原因,当前的方法仍然非常快,尽管我预计会有很多不同大小的分配(取决于行数)并因此导致碎片化。

让我们回到基础

循环缓冲区通常保证没有碎片,因为它不是按存储的内容键入的,而是按大小键入的。例如,您可以定义1MB循环缓冲区。对于固定长度类型,这为您提供了可以存储的固定数量的元素

你显然没有这样做。通过将
Vec
存储为一个元素,即使整个数组的长度是固定的,内容也不是固定的。存储在数组中的每个元素都是一个胖指针,指向
Vec
(起点和长度)

当然,当您插入时,您必须:

  • 创建这个
    Vec
    (这是您正在考虑的碎片,但并没有真正看到,因为rust分配器在这方面非常有效)
  • 将vec插入它应该位于的位置,如果有必要,将所有内容侧向移动(这里使用标准的循环缓冲技术)
  • 第二个选项是实际的循环缓冲区。固定大小和零allocs会增加,如果操作正确,则会失去存储整行的能力,而100%保证在缓冲区的开头有整行

    在我们进入DYI广阔的土地之前,一个快速的指针已经准备好了。这是您实现的更优化的版本,尽管有一些(完全保证的)
    不安全的部分


    实现我们自己的循环缓冲区 我们将做出一系列假设,并为此设定一些要求:

    • 我们希望能够存储大字符串
    • 我们的缓冲区存储字节。因此,整个堆栈处理的是所拥有的
      u8
    • 我们将使用一个简单的
      Vec
      ;实际上,您根本不会重新实现整个结构,阵列只是为了演示
    这些选择的结果是以下元素结构:

    | Element size | Data     |
    |--------------|----------|
    |  4 bytes     |  N bytes |
    
    因此,我们在每条消息之前丢失了4个字节,以便能够获得指向下一个元素的清晰指针/跳过引用(最大大小与
    u32
    相当)

    一个简单的实现示例如下():

    使用字节顺序::{NativeEndian,readbytetext,writebytextest};
    发布结构循环缓冲区{
    资料来源:Vec,
    尾巴:用,
    元素:usize,
    }
    impl循环缓冲器{
    pub fn new(最大值:usize)->Self{
    循环缓冲器{
    数据:Vec::具有最大容量,
    元素:0,
    尾:0,
    }
    }
    ///缓冲区中元素的数量
    发布fn元素(&self)->使用{
    自我要素
    }
    ///缓冲区中使用的字节数,包括元数据
    pub fn len(&self)->使用{
    尾巴
    }
    ///ringbuffer中第一个元素的长度
    pub fn next_element_len(&self)->选项{
    自我数据
    .get(0..4)
    .然后(| mut v | v.read_u32::().ok().map(| r | r as usize))
    }
    ///移除ringbuffer中的第一个元素(换行)
    pub fn pop(&mut self)->选项{
    self.next_element_len().map(| chunk_size|{
    self.tail-=块大小+4;
    self.elements-=1;
    自我数据
    .拼接(..(块大小+4),向量![])
    .skip(4)
    .collect()
    })
    }
    pub fn get(&self,idx:usize)->选项{
    如果自选元素的容量{
    self.pop();
    }
    self.data.write_u32::(e_len as u32.unwrap();
    self.data.append(&mut元素);
    self.tail+=4+e_len;
    自身元素+=1;
    println!(“{:?}”,self.data);
    }
    }
    
    请再次注意,这是一个针对show的幼稚的实现