mirror of
https://github.com/red-prig/fpPS4.git
synced 2024-11-27 16:40:43 +00:00
462 lines
8.3 KiB
ObjectPascal
462 lines
8.3 KiB
ObjectPascal
{ Implimentation of Dmitry Vyukov Intrusive MPSC node-based queue on free pascal
|
|
|
|
Copyright (C) 2018-2020 Red_prig
|
|
|
|
This library is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version with the following modification:
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent modules,and
|
|
to copy and distribute the resulting executable under terms of your choice,
|
|
provided that you also meet, for each linked independent module, the terms
|
|
and conditions of the license of that module. An independent module is a
|
|
module which is not derived from or based on this library. If you modify
|
|
this library, you may extend this exception to your version of the library,
|
|
but you are not obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
|
|
for more details.
|
|
}
|
|
|
|
unit LFQueue;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
Const
|
|
CacheLineSize=64;
|
|
|
|
Type
|
|
TIntrusiveMPSCQueue=object
|
|
protected
|
|
type
|
|
PQNode=^TQNode;
|
|
TQNode=record
|
|
next_:PQNode;
|
|
//some data
|
|
end;
|
|
Var
|
|
tail_:PQNode;
|
|
stub_:TQNode;
|
|
head_:PQNode;
|
|
public
|
|
Procedure Create;
|
|
Function Push(Node:Pointer):Boolean;
|
|
Function Pop(Var Node:Pointer):Boolean;
|
|
Function IsEmpty:Boolean; inline;
|
|
end;
|
|
|
|
TIntrusiveMPSCQueueA=object
|
|
protected
|
|
type
|
|
PQNode=^TQNode;
|
|
TQNode=record
|
|
next_:PQNode;
|
|
//some data
|
|
end;
|
|
Var
|
|
tail_:record
|
|
Case Byte of
|
|
0:(Pad:Array[0..CacheLineSize-1] of Byte);
|
|
1:(VAL:PQNode;stub:TQNode);
|
|
end;
|
|
head_:record
|
|
Case Byte of
|
|
0:(Pad:Array[0..CacheLineSize-1] of Byte);
|
|
1:(VAL:PQNode);
|
|
end;
|
|
public
|
|
Procedure Create;
|
|
Function Push(Node:Pointer):Boolean;
|
|
Function Pop(Var Node:Pointer):Boolean;
|
|
Function IsEmpty:Boolean; inline;
|
|
end;
|
|
|
|
generic TLFQueue<TItem,Allocator,back_off>=object(TIntrusiveMPSCQueue)
|
|
type
|
|
PNode=^TNode;
|
|
TNode=record
|
|
next_:PNode;
|
|
Item:TItem;
|
|
end;
|
|
Function push_front(Const val:TItem):Boolean;
|
|
Function pop_back(Var val:TItem):Boolean;
|
|
end;
|
|
|
|
generic TLFQueueA<TItem,Allocator,back_off>=object(TIntrusiveMPSCQueueA)
|
|
type
|
|
PNode=^TNode;
|
|
TNode=record
|
|
next_:PNode;
|
|
Item:TItem;
|
|
end;
|
|
Function push_front(Const val:TItem):Boolean;
|
|
Function pop_back(Var val:TItem):Boolean;
|
|
end;
|
|
|
|
function load_consume(Var addr:Pointer):Pointer; inline;
|
|
function load_consume(Var addr:PtrUInt):PtrUInt; inline;
|
|
Procedure store_release(Var addr:Pointer;v:Pointer); inline;
|
|
function XCHG(Var addr:Pointer;New:Pointer):Pointer; inline;
|
|
|
|
implementation
|
|
|
|
function load_consume(Var addr:Pointer):Pointer; inline;
|
|
begin
|
|
ReadDependencyBarrier;
|
|
Result:=addr;
|
|
end;
|
|
|
|
function load_consume(Var addr:PtrUInt):PtrUInt; inline;
|
|
begin
|
|
ReadDependencyBarrier;
|
|
Result:=addr;
|
|
end;
|
|
|
|
Procedure store_release(Var addr:Pointer;v:Pointer); inline;
|
|
begin
|
|
WriteBarrier;
|
|
addr:=v;
|
|
end;
|
|
|
|
function XCHG(Var addr:Pointer;New:Pointer):Pointer; inline;
|
|
begin
|
|
Result:=System.InterLockedExchange(addr,New);
|
|
end;
|
|
|
|
//
|
|
|
|
Procedure TIntrusiveMPSCQueue.Create;
|
|
begin
|
|
FillChar(Self,SizeOf(Self),0);
|
|
head_:=@stub_;
|
|
tail_:=@stub_;
|
|
ReadWriteBarrier;
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueue.Push(Node:Pointer):Boolean;
|
|
Var
|
|
prev:PQNode;
|
|
begin
|
|
if not Assigned(Node) then Exit(False);
|
|
store_release(PQNode(Node)^.next_,nil);
|
|
prev:=XCHG(head_,Node);
|
|
store_release(prev^.next_,Node);
|
|
Result:=True;
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueue.Pop(Var Node:Pointer):Boolean;
|
|
Var
|
|
tail,n,head:PQNode;
|
|
begin
|
|
Node:=nil;
|
|
Result:=False;
|
|
|
|
tail:=tail_;
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if tail=@stub_ then
|
|
begin
|
|
if n=nil then Exit;
|
|
store_release(tail_,n);
|
|
tail:=n;
|
|
n:=load_consume(n^.next_);
|
|
end;
|
|
|
|
if n<>nil then
|
|
begin
|
|
store_release(tail_,n);
|
|
Node:=tail;
|
|
store_release(tail^.next_,nil);
|
|
Exit(True);
|
|
end;
|
|
|
|
head:=head_;
|
|
if tail<>head then Exit;
|
|
|
|
stub_.next_:=nil;
|
|
n:=XCHG(head_,@stub_);
|
|
store_release(n^.next_,@stub_);
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if n<>nil then
|
|
begin
|
|
store_release(tail_,n);
|
|
Node:=tail;
|
|
store_release(tail^.next_,nil);
|
|
Exit(True);
|
|
end;
|
|
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueue.IsEmpty:Boolean; inline;
|
|
begin
|
|
Result:=head_=@stub_;
|
|
end;
|
|
|
|
//
|
|
|
|
Procedure TIntrusiveMPSCQueueA.Create;
|
|
begin
|
|
FillChar(Self,SizeOf(Self),0);
|
|
head_.VAL:=@tail_.stub;
|
|
tail_.VAL:=@tail_.stub;
|
|
ReadWriteBarrier;
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueueA.Push(Node:Pointer):Boolean;
|
|
Var
|
|
prev:PQNode;
|
|
begin
|
|
if not Assigned(Node) then Exit(False);
|
|
store_release(PQNode(Node)^.next_,nil);
|
|
prev:=XCHG(head_.VAL,Node);
|
|
store_release(prev^.next_,Node);
|
|
Result:=True;
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueueA.Pop(Var Node:Pointer):Boolean;
|
|
Var
|
|
tail,n,head:PQNode;
|
|
begin
|
|
Node:=nil;
|
|
Result:=False;
|
|
|
|
tail:=tail_.VAL;
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if tail=@tail_.stub then
|
|
begin
|
|
if n=nil then Exit;
|
|
store_release(tail_.VAL,n);
|
|
tail:=n;
|
|
n:=load_consume(n^.next_);
|
|
end;
|
|
|
|
if n<>nil then
|
|
begin
|
|
store_release(tail_.VAL,n);
|
|
Node:=tail;
|
|
store_release(tail^.next_,nil);
|
|
Exit(True);
|
|
end;
|
|
|
|
head:=head_.VAL;
|
|
if tail<>head then Exit;
|
|
|
|
tail_.stub.next_:=nil;
|
|
n:=XCHG(head_.VAL,@tail_.stub);
|
|
store_release(n^.next_,@tail_.stub);
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if n<>nil then
|
|
begin
|
|
store_release(tail_.VAL,n);
|
|
Node:=tail;
|
|
store_release(tail^.next_,nil);
|
|
Exit(True);
|
|
end;
|
|
|
|
end;
|
|
|
|
Function TIntrusiveMPSCQueueA.IsEmpty:Boolean; inline;
|
|
begin
|
|
Result:=head_.VAL=@tail_.stub;
|
|
end;
|
|
|
|
//
|
|
|
|
Function TLFQueue.push_front(Const val:TItem):Boolean;
|
|
Var
|
|
Node:PNode;
|
|
begin
|
|
Node:=Allocator.AllocMem(SizeOf(TNode));
|
|
Result:=Push(Node);
|
|
end;
|
|
|
|
Function TLFQueue.pop_back(Var val:TItem):Boolean;
|
|
Var
|
|
tail,n,head:PQNode;
|
|
bkoff:back_off;
|
|
begin
|
|
Result:=False;
|
|
|
|
bkoff.Reset;
|
|
repeat
|
|
tail:=XCHG(tail_,nil);
|
|
|
|
if (tail<>nil) then
|
|
begin
|
|
Break;
|
|
end else
|
|
begin
|
|
bkoff.Wait;
|
|
end;
|
|
|
|
until false;
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if tail=@stub_ then
|
|
begin
|
|
if n=nil then
|
|
begin
|
|
|
|
if tail=nil then tail:=@stub_;
|
|
store_release(tail_,tail); //unlock
|
|
|
|
Exit;
|
|
end;
|
|
tail:=n;
|
|
n:=load_consume(n^.next_);
|
|
end;
|
|
|
|
if n<>nil then
|
|
begin
|
|
|
|
val:=PNode(tail)^.Item;
|
|
FreeMem(tail);
|
|
|
|
if n=nil then n:=@stub_;
|
|
store_release(tail_,n); //unlock
|
|
|
|
Exit(True);
|
|
end;
|
|
|
|
head:=head_;
|
|
if tail<>head then
|
|
begin
|
|
|
|
if tail=nil then tail:=@stub_;
|
|
store_release(tail_,tail); //unlock
|
|
|
|
Exit;
|
|
end;
|
|
|
|
stub_.next_:=nil;
|
|
n:=XCHG(head_,@stub_);
|
|
store_release(n^.next_,@stub_);
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if n<>nil then
|
|
begin
|
|
|
|
val:=PNode(tail)^.Item;
|
|
FreeMem(tail);
|
|
|
|
if n=nil then n:=@stub_;
|
|
store_release(tail_,n); //unlock
|
|
|
|
Exit(True);
|
|
end;
|
|
|
|
if tail=nil then tail:=@stub_;
|
|
store_release(tail_,tail); //unlock
|
|
end;
|
|
|
|
//
|
|
|
|
Function TLFQueueA.push_front(Const val:TItem):Boolean;
|
|
Var
|
|
Node:PNode;
|
|
begin
|
|
Node:=Allocator.AllocMem(SizeOf(TNode));
|
|
Result:=Push(Node);
|
|
end;
|
|
|
|
Function TLFQueueA.pop_back(Var val:TItem):Boolean;
|
|
Var
|
|
tail,n,head:PQNode;
|
|
bkoff:back_off;
|
|
begin
|
|
Result:=False;
|
|
|
|
bkoff.Reset;
|
|
repeat
|
|
tail:=XCHG(tail_.VAL,nil);
|
|
|
|
if (tail<>nil) then
|
|
begin
|
|
Break;
|
|
end else
|
|
begin
|
|
bkoff.Wait;
|
|
end;
|
|
|
|
until false;
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if tail=@tail_.stub then
|
|
begin
|
|
if n=nil then
|
|
begin
|
|
|
|
if tail=nil then tail:=@tail_.stub;
|
|
store_release(tail_.VAL,tail); //unlock
|
|
|
|
Exit;
|
|
end;
|
|
tail:=n;
|
|
n:=load_consume(n^.next_);
|
|
end;
|
|
|
|
if n<>nil then
|
|
begin
|
|
|
|
val:=PNode(tail)^.Item;
|
|
FreeMem(tail);
|
|
|
|
if n=nil then n:=@tail_.stub;
|
|
store_release(tail_.VAL,n); //unlock
|
|
|
|
Exit(True);
|
|
end;
|
|
|
|
head:=head_.VAL;
|
|
if tail<>head then
|
|
begin
|
|
|
|
if tail=nil then tail:=@tail_.stub;
|
|
store_release(tail_.VAL,tail); //unlock
|
|
|
|
Exit;
|
|
end;
|
|
|
|
tail_.stub.next_:=nil;
|
|
n:=XCHG(head_.VAL,@tail_.stub);
|
|
store_release(n^.next_,@tail_.stub);
|
|
|
|
n:=load_consume(tail^.next_);
|
|
|
|
if n<>nil then
|
|
begin
|
|
|
|
val:=PNode(tail)^.Item;
|
|
FreeMem(tail);
|
|
|
|
if n=nil then n:=@tail_.stub;
|
|
store_release(tail_.VAL,n); //unlock
|
|
|
|
Exit(True);
|
|
end;
|
|
|
|
if tail=nil then tail:=@tail_.stub;
|
|
store_release(tail_.VAL,tail); //unlock
|
|
end;
|
|
|
|
end.
|
|
|
|
|
|
|