|
@@ -1,8 +1,12 @@
|
|
|
program raw;
|
|
|
|
|
|
{
|
|
|
-TechEmpower framework benchmarks implementation
|
|
|
-See https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
|
|
|
+ ----------------------------------------------------
|
|
|
+ TechEmpower Framework Benchmarks implementation
|
|
|
+ in modern pascal and the mORMot 2 framework
|
|
|
+ ----------------------------------------------------
|
|
|
+ https://github.com/TechEmpower/FrameworkBenchmarks/wiki
|
|
|
+ command line optional syntax: raw [threads] [cores] [servers]
|
|
|
}
|
|
|
|
|
|
{$I mormot.defines.inc}
|
|
@@ -14,7 +18,6 @@ uses
|
|
|
{$I mormot.uses.inc} // include mormot.core.fpcx64mm or mormot.core.fpclibcmm
|
|
|
sysutils,
|
|
|
classes,
|
|
|
- BaseUnix,
|
|
|
mormot.core.base,
|
|
|
mormot.core.os,
|
|
|
mormot.core.rtti,
|
|
@@ -28,16 +31,17 @@ uses
|
|
|
mormot.core.perf,
|
|
|
mormot.core.mustache,
|
|
|
mormot.orm.core,
|
|
|
+ mormot.orm.base,
|
|
|
mormot.orm.sql,
|
|
|
mormot.db.core,
|
|
|
mormot.db.raw.sqlite3,
|
|
|
mormot.db.raw.sqlite3.static,
|
|
|
+ mormot.db.sql,
|
|
|
+ mormot.db.sql.postgres,
|
|
|
mormot.rest.sqlite3,
|
|
|
mormot.net.http,
|
|
|
mormot.net.server,
|
|
|
- mormot.net.async,
|
|
|
- mormot.db.sql,
|
|
|
- mormot.db.sql.postgres;
|
|
|
+ mormot.net.async;
|
|
|
|
|
|
type
|
|
|
// data structures
|
|
@@ -65,7 +69,6 @@ type
|
|
|
end;
|
|
|
TOrmCachedWorld = class(TOrmWorld);
|
|
|
TOrmWorlds = array of TOrmWorld;
|
|
|
- TOrmWorldClass = class of TOrmWorld;
|
|
|
TOrmFortune = class(TOrm)
|
|
|
protected
|
|
|
fMessage: RawUtf8;
|
|
@@ -84,11 +87,8 @@ type
|
|
|
fStore: TRestServerDB;
|
|
|
fTemplate: TSynMustache;
|
|
|
protected
|
|
|
- // as used by rawqueries and rawupdates
|
|
|
- function getRawRandomWorlds(cnt: PtrInt; out res: TWorlds): boolean;
|
|
|
- // implements /queries and /cached-queries endpoints
|
|
|
- function doqueries(ctxt: THttpServerRequest; orm: TOrmWorldClass;
|
|
|
- const search: RawUtf8): cardinal;
|
|
|
+ // as used by /rawqueries and /rawupdates
|
|
|
+ function GetRawRandomWorlds(cnt: PtrInt; out res: TWorlds): boolean;
|
|
|
public
|
|
|
constructor Create(threadCount: integer; flags: THttpServerOptions); reintroduce;
|
|
|
destructor Destroy; override;
|
|
@@ -108,45 +108,44 @@ type
|
|
|
end;
|
|
|
|
|
|
const
|
|
|
- TEXT_CONTENT_TYPE_NO_ENCODING: RawUtf8 = 'text/plain';
|
|
|
HELLO_WORLD: RawUtf8 = 'Hello, World!';
|
|
|
- WORLD_COUNT = 10000;
|
|
|
+ TEXT_CONTENT_TYPE_NO_ENCODING: RawUtf8 = 'text/plain';
|
|
|
|
|
|
- WORLD_READ_SQL = 'select id,randomNumber from World where id=?';
|
|
|
+ WORLD_COUNT = 10000;
|
|
|
+ WORLD_READ_SQL = 'select id,randomNumber from World where id=?';
|
|
|
WORLD_UPDATE_SQLN ='update World as t set randomNumber = v.r from ' +
|
|
|
'(SELECT unnest(?::bigint[]), unnest(?::bigint[]) order by 1) as v(id, r)' +
|
|
|
' where t.id = v.id';
|
|
|
- FORTUNES_SQL = 'select id,message from Fortune';
|
|
|
+ FORTUNES_SQL = 'select id,message from Fortune';
|
|
|
|
|
|
FORTUNES_MESSAGE = 'Additional fortune added at request time.';
|
|
|
- FORTUNES_TPL = '<!DOCTYPE html>' +
|
|
|
- '<html>' +
|
|
|
- '<head><title>Fortunes</title></head>' +
|
|
|
- '<body>' +
|
|
|
- '<table>' +
|
|
|
- '<tr><th>id</th><th>message</th></tr>' +
|
|
|
- '{{#.}}' +
|
|
|
- '<tr><td>{{id}}</td><td>{{message}}</td></tr>' +
|
|
|
- '{{/.}}' +
|
|
|
- '</table>' +
|
|
|
- '</body>' +
|
|
|
- '</html>';
|
|
|
-
|
|
|
-
|
|
|
-function RandomWorld: integer; inline;
|
|
|
+ FORTUNES_TPL = '<!DOCTYPE html>' +
|
|
|
+ '<html>' +
|
|
|
+ '<head><title>Fortunes</title></head>' +
|
|
|
+ '<body>' +
|
|
|
+ '<table>' +
|
|
|
+ '<tr><th>id</th><th>message</th></tr>' +
|
|
|
+ '{{#.}}' +
|
|
|
+ '<tr><td>{{id}}</td><td>{{message}}</td></tr>' +
|
|
|
+ '{{/.}}' +
|
|
|
+ '</table>' +
|
|
|
+ '</body>' +
|
|
|
+ '</html>';
|
|
|
+
|
|
|
+
|
|
|
+function ComputeRandomWorld: integer; inline;
|
|
|
begin
|
|
|
result := Random32(WORLD_COUNT) + 1;
|
|
|
end;
|
|
|
|
|
|
-function getQueriesParamValue(ctxt: THttpServerRequest;
|
|
|
+function GetQueriesParamValue(ctxt: THttpServerRequest;
|
|
|
const search: RawUtf8 = 'QUERIES='): cardinal;
|
|
|
begin
|
|
|
- if not ctxt.UrlParam(search, result) then
|
|
|
+ if not ctxt.UrlParam(search, result) or
|
|
|
+ (result = 0) then
|
|
|
result := 1
|
|
|
else if result > 500 then
|
|
|
- result := 500
|
|
|
- else if result < 1 then
|
|
|
- result := 1;
|
|
|
+ result := 500;
|
|
|
end;
|
|
|
|
|
|
|
|
@@ -160,10 +159,10 @@ begin
|
|
|
'tfb-database:5432', 'hello_world', 'benchmarkdbuser', 'benchmarkdbpass');
|
|
|
// customize JSON serialization for TFB expectations
|
|
|
TOrmWorld.OrmProps.Fields.JsonRenameProperties([
|
|
|
- 'ID', 'id',
|
|
|
+ 'ID', 'id',
|
|
|
'RandomNumber', 'randomNumber']);
|
|
|
TOrmCachedWorld.OrmProps.Fields.JsonRenameProperties([
|
|
|
- 'ID', 'id',
|
|
|
+ 'ID', 'id',
|
|
|
'RandomNumber', 'randomNumber']);
|
|
|
// setup the ORM data model
|
|
|
fModel := TOrmModel.Create([TOrmWorld, TOrmFortune, TOrmCachedWorld]);
|
|
@@ -186,8 +185,8 @@ begin
|
|
|
[hsoNoXPoweredHeader, // not needed for a benchmark
|
|
|
hsoHeadersInterning, // reduce memory contention for /plaintext and /json
|
|
|
hsoNoStats, // disable low-level statistic counters
|
|
|
- //hsoThreadCpuAffinity, // better scaling of /plaintext in some cases
|
|
|
- hsoReusePort, // allow several processes binding on the same port
|
|
|
+ //hsoThreadCpuAffinity, // worse scaling on multi-servers
|
|
|
+ hsoThreadSmooting, // seems a good option, even if not magical
|
|
|
{$ifdef WITH_LOGS}
|
|
|
hsoLogVerbose,
|
|
|
{$endif WITH_LOGS}
|
|
@@ -196,8 +195,8 @@ begin
|
|
|
fHttpServer.HttpQueueLength := 10000; // needed e.g. from wrk/ab benchmarks
|
|
|
// use default routing using RTTI on the TRawAsyncServer published methods
|
|
|
fHttpServer.Route.RunMethods([urmGet], self);
|
|
|
- // writeln(fHttpServer.Route.Tree[urmGet].ToText);
|
|
|
- fHttpServer.WaitStarted; // raise exception e.g. on binding issue
|
|
|
+ // wait for the server to be ready and raise exception e.g. on binding issue
|
|
|
+ fHttpServer.WaitStarted;
|
|
|
end;
|
|
|
|
|
|
destructor TRawAsyncServer.Destroy;
|
|
@@ -209,66 +208,9 @@ begin
|
|
|
inherited Destroy;
|
|
|
end;
|
|
|
|
|
|
-function TRawAsyncServer.plaintext(ctxt: THttpServerRequest): cardinal;
|
|
|
-begin
|
|
|
- ctxt.OutContentType := TEXT_CONTENT_TYPE_NO_ENCODING;
|
|
|
- ctxt.OutContent := HELLO_WORLD;
|
|
|
- result := HTTP_SUCCESS;
|
|
|
-end;
|
|
|
-
|
|
|
-function TRawAsyncServer.json(ctxt: THttpServerRequest): cardinal;
|
|
|
-var
|
|
|
- msgRec: TMessageRec;
|
|
|
-begin
|
|
|
- msgRec.message := HELLO_WORLD;
|
|
|
- ctxt.SetOutJson(@msgRec, TypeInfo(TMessageRec));
|
|
|
- result := HTTP_SUCCESS;
|
|
|
-end;
|
|
|
+// query DB world table for /rawqueries and /rawupdates endpoints
|
|
|
|
|
|
-function TRawAsyncServer.rawdb(ctxt: THttpServerRequest): cardinal;
|
|
|
-var
|
|
|
- conn: TSqlDBConnection;
|
|
|
- stmt: ISQLDBStatement;
|
|
|
-begin
|
|
|
- result := HTTP_SERVERERROR;
|
|
|
- conn := fDbPool.ThreadSafeConnection;
|
|
|
- stmt := conn.NewStatementPrepared(WORLD_READ_SQL, true, true);
|
|
|
- stmt.Bind(1, RandomWorld);
|
|
|
- stmt.ExecutePrepared;
|
|
|
- if stmt.Step then
|
|
|
- begin
|
|
|
- ctxt.SetOutJson(
|
|
|
- '{"id":%,"randomNumber":%}', [stmt.ColumnInt(0), stmt.ColumnInt(1)]);
|
|
|
- result := HTTP_SUCCESS;
|
|
|
- stmt.ReleaseRows;
|
|
|
- end;
|
|
|
- stmt := nil;
|
|
|
-end;
|
|
|
-
|
|
|
-function TRawAsyncServer.db(ctxt: THttpServerRequest): cardinal;
|
|
|
-var
|
|
|
- w: TOrmWorld;
|
|
|
-begin
|
|
|
- w := TOrmWorld.Create(fStore.Orm, RandomWorld);
|
|
|
- try
|
|
|
- ctxt.SetOutJson(w);
|
|
|
- result := HTTP_SUCCESS;
|
|
|
- finally
|
|
|
- w.Free;
|
|
|
- end;
|
|
|
-end;
|
|
|
-
|
|
|
-function TRawAsyncServer.queries(ctxt: THttpServerRequest): cardinal;
|
|
|
-begin
|
|
|
- result := doqueries(ctxt, TOrmWorld, 'QUERIES=');
|
|
|
-end;
|
|
|
-
|
|
|
-function TRawAsyncServer.cached_queries(ctxt: THttpServerRequest): cardinal;
|
|
|
-begin
|
|
|
- result := doqueries(ctxt, TOrmCachedWorld, 'COUNT=');
|
|
|
-end;
|
|
|
-
|
|
|
-function TRawAsyncServer.getRawRandomWorlds(cnt: PtrInt; out res: TWorlds): boolean;
|
|
|
+function TRawAsyncServer.GetRawRandomWorlds(cnt: PtrInt; out res: TWorlds): boolean;
|
|
|
var
|
|
|
conn: TSqlDBConnection;
|
|
|
stmt: ISQLDBStatement;
|
|
@@ -280,15 +222,14 @@ begin
|
|
|
SetLength(res{%H-}, cnt);
|
|
|
conn := fDbPool.ThreadSafeConnection;
|
|
|
// specific code to use PostgresSQL pipelining mode
|
|
|
- // see test_multi_pipelines in
|
|
|
+ // see test_nosync in
|
|
|
// https://github.com/postgres/postgres/blob/master/src/test/modules/libpq_pipeline/libpq_pipeline.c
|
|
|
stmt := conn.NewStatementPrepared(WORLD_READ_SQL, true, true);
|
|
|
- //conn.StartTransaction;
|
|
|
pConn.EnterPipelineMode;
|
|
|
pStmt := TSqlDBPostgresStatement(stmt.Instance);
|
|
|
for i := 0 to cnt - 1 do
|
|
|
begin
|
|
|
- pStmt.Bind(1, RandomWorld);
|
|
|
+ pStmt.Bind(1, ComputeRandomWorld);
|
|
|
pStmt.SendPipelinePrepared;
|
|
|
pConn.PipelineSync;
|
|
|
end;
|
|
@@ -303,42 +244,67 @@ begin
|
|
|
pConn.CheckPipelineSync;
|
|
|
end;
|
|
|
pConn.ExitPipelineMode;
|
|
|
- //conn.commit;
|
|
|
result := true;
|
|
|
end;
|
|
|
|
|
|
-function TRawAsyncServer.rawqueries(ctxt: THttpServerRequest): cardinal;
|
|
|
+// following methods implement the server endpoints
|
|
|
+
|
|
|
+function TRawAsyncServer.plaintext(ctxt: THttpServerRequest): cardinal;
|
|
|
+begin
|
|
|
+ ctxt.OutContent := HELLO_WORLD;
|
|
|
+ ctxt.OutContentType := TEXT_CONTENT_TYPE_NO_ENCODING;
|
|
|
+ result := HTTP_SUCCESS;
|
|
|
+end;
|
|
|
+
|
|
|
+function TRawAsyncServer.json(ctxt: THttpServerRequest): cardinal;
|
|
|
var
|
|
|
- cnt: PtrInt;
|
|
|
- res: TWorlds;
|
|
|
+ msgRec: TMessageRec;
|
|
|
begin
|
|
|
- cnt := getQueriesParamValue(ctxt);
|
|
|
- if not getRawRandomWorlds(cnt, res) then
|
|
|
- exit(HTTP_SERVERERROR);
|
|
|
- ctxt.SetOutJson(@res, TypeInfo(TWorlds));
|
|
|
+ msgRec.message := HELLO_WORLD;
|
|
|
+ ctxt.SetOutJson(@msgRec, TypeInfo(TMessageRec));
|
|
|
result := HTTP_SUCCESS;
|
|
|
end;
|
|
|
|
|
|
-function TRawAsyncServer.doqueries(ctxt: THttpServerRequest;
|
|
|
- orm: TOrmWorldClass; const search: RawUtf8): cardinal;
|
|
|
+function TRawAsyncServer.db(ctxt: THttpServerRequest): cardinal;
|
|
|
var
|
|
|
- cnt, i: PtrInt;
|
|
|
- res: TOrmWorlds;
|
|
|
+ w: TOrmWorld;
|
|
|
begin
|
|
|
- result := HTTP_SERVERERROR;
|
|
|
- cnt := getQueriesParamValue(ctxt, search);
|
|
|
- SetLength(res, cnt);
|
|
|
- for i := 0 to cnt - 1 do
|
|
|
- begin
|
|
|
- res[i] := orm.Create; // TOrmWorld or TOrmCachedWorld
|
|
|
- if not fStore.Orm.Retrieve(RandomWorld, res[i]) then
|
|
|
- exit;
|
|
|
+ w := TOrmWorld.Create(fStore.Orm, ComputeRandomWorld);
|
|
|
+ try
|
|
|
+ ctxt.SetOutJson(w);
|
|
|
+ result := HTTP_SUCCESS;
|
|
|
+ finally
|
|
|
+ w.Free;
|
|
|
end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TRawAsyncServer.queries(ctxt: THttpServerRequest): cardinal;
|
|
|
+var
|
|
|
+ i: PtrInt;
|
|
|
+ res: TOrmWorlds;
|
|
|
+begin
|
|
|
+ SetLength(res, GetQueriesParamValue(ctxt, 'QUERIES='));
|
|
|
+ for i := 0 to length(res) - 1 do
|
|
|
+ res[i] := TOrmWorld.Create(fStore.Orm, ComputeRandomWorld);
|
|
|
ctxt.SetOutJson(@res, TypeInfo(TOrmWorlds));
|
|
|
ObjArrayClear(res);
|
|
|
result := HTTP_SUCCESS;
|
|
|
end;
|
|
|
|
|
|
+function TRawAsyncServer.cached_queries(ctxt: THttpServerRequest): cardinal;
|
|
|
+var
|
|
|
+ i: PtrInt;
|
|
|
+ res: TOrmWorlds;
|
|
|
+ cache: POrmCacheEntry;
|
|
|
+begin
|
|
|
+ cache := fStore.Orm.Cache.Table(TOrmCachedWorld);
|
|
|
+ SetLength(res, GetQueriesParamValue(ctxt, 'COUNT='));
|
|
|
+ for i := 0 to length(res) - 1 do
|
|
|
+ res[i] := cache.Get(ComputeRandomWorld);
|
|
|
+ ctxt.SetOutJson(@res, TypeInfo(TOrmWorlds));
|
|
|
+ result := HTTP_SUCCESS;
|
|
|
+end;
|
|
|
+
|
|
|
function OrmFortuneCompareByMessage(const A, B): integer;
|
|
|
begin
|
|
|
result := StrComp(pointer(TOrmFortune(A).Message), pointer(TOrmFortune(B).Message));
|
|
@@ -366,6 +332,66 @@ begin
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
+function TRawAsyncServer.updates(ctxt: THttpServerRequest): cardinal;
|
|
|
+var
|
|
|
+ i: PtrInt;
|
|
|
+ res: TOrmWorlds;
|
|
|
+ w: TOrmWorld;
|
|
|
+ b: TRestBatch;
|
|
|
+begin
|
|
|
+ result := HTTP_SERVERERROR;
|
|
|
+ SetLength(res, GetQueriesParamValue(ctxt));
|
|
|
+ b := TRestBatch.Create(fStore.ORM, TOrmWorld, {transrows=}0,
|
|
|
+ [boExtendedJson, boNoModelEncoding, boPutNoCacheFlush]);
|
|
|
+ try
|
|
|
+ for i := 0 to length(res) - 1 do
|
|
|
+ begin
|
|
|
+ w := TOrmWorld.Create;
|
|
|
+ res[i] := w;
|
|
|
+ if not fStore.Orm.Retrieve(ComputeRandomWorld, w) then
|
|
|
+ exit;
|
|
|
+ w.RandomNumber := ComputeRandomWorld;
|
|
|
+ b.Update(w);
|
|
|
+ end;
|
|
|
+ result := b.Send;
|
|
|
+ if result = HTTP_SUCCESS then
|
|
|
+ ctxt.SetOutJson(@res, TypeInfo(TOrmWorlds));
|
|
|
+ finally
|
|
|
+ b.Free;
|
|
|
+ ObjArrayClear(res);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TRawAsyncServer.rawdb(ctxt: THttpServerRequest): cardinal;
|
|
|
+var
|
|
|
+ conn: TSqlDBConnection;
|
|
|
+ stmt: ISQLDBStatement;
|
|
|
+begin
|
|
|
+ result := HTTP_SERVERERROR;
|
|
|
+ conn := fDbPool.ThreadSafeConnection;
|
|
|
+ stmt := conn.NewStatementPrepared(WORLD_READ_SQL, true, true);
|
|
|
+ stmt.Bind(1, ComputeRandomWorld);
|
|
|
+ stmt.ExecutePrepared;
|
|
|
+ if stmt.Step then
|
|
|
+ begin
|
|
|
+ ctxt.SetOutJson(
|
|
|
+ '{"id":%,"randomNumber":%}', [stmt.ColumnInt(0), stmt.ColumnInt(1)]);
|
|
|
+ result := HTTP_SUCCESS;
|
|
|
+ stmt.ReleaseRows;
|
|
|
+ end;
|
|
|
+ stmt := nil;
|
|
|
+end;
|
|
|
+
|
|
|
+function TRawAsyncServer.rawqueries(ctxt: THttpServerRequest): cardinal;
|
|
|
+var
|
|
|
+ res: TWorlds;
|
|
|
+begin
|
|
|
+ if not GetRawRandomWorlds(GetQueriesParamValue(ctxt), res) then
|
|
|
+ exit(HTTP_SERVERERROR);
|
|
|
+ ctxt.SetOutJson(@res, TypeInfo(TWorlds));
|
|
|
+ result := HTTP_SUCCESS;
|
|
|
+end;
|
|
|
+
|
|
|
function FortuneCompareByMessage(const A, B): integer;
|
|
|
begin
|
|
|
result := StrComp(pointer(TFortune(A).message), pointer(TFortune(B).message));
|
|
@@ -399,41 +425,51 @@ begin
|
|
|
result := HTTP_SUCCESS;
|
|
|
end;
|
|
|
|
|
|
-function TRawAsyncServer.updates(ctxt: THttpServerRequest): cardinal;
|
|
|
var
|
|
|
- cnt, i: PtrInt;
|
|
|
- res: TOrmWorlds;
|
|
|
- w: TOrmWorld;
|
|
|
- b: TRestBatch;
|
|
|
+ LastComputeUpdateSql: RawUtf8;
|
|
|
+ LastComputeUpdateSqlCnt: integer;
|
|
|
+ LastComputeUpdateSqlSafe: TLightLock;
|
|
|
+
|
|
|
+function ComputeUpdateSql(cnt: integer): RawUtf8;
|
|
|
+var
|
|
|
+ i: integer;
|
|
|
+ W: TTextWriter;
|
|
|
+ tmp: TTextWriterStackBuffer;
|
|
|
begin
|
|
|
- result := HTTP_SERVERERROR;
|
|
|
- cnt := getQueriesParamValue(ctxt);
|
|
|
- SetLength(res, cnt);
|
|
|
- b := TRestBatch.Create(fStore.ORM, TOrmWorld, {transrows=}0,
|
|
|
- [boExtendedJson, boNoModelEncoding, boPutNoCacheFlush]);
|
|
|
- try
|
|
|
- for i := 0 to cnt - 1 do
|
|
|
- begin
|
|
|
- w := TOrmWorld.Create;
|
|
|
- res[i] := w;
|
|
|
- if not fStore.Orm.Retrieve(RandomWorld, w) then
|
|
|
- exit;
|
|
|
- w.RandomNumber := RandomWorld;
|
|
|
- b.Update(w);
|
|
|
+ LastComputeUpdateSqlSafe.Lock;
|
|
|
+ if cnt <> LastComputeUpdateSqlCnt then
|
|
|
+ begin
|
|
|
+ // update table set randomNumber = CASE id when ? then ? when ? then ? ...
|
|
|
+ // when ? then ? else randomNumber end where id in (?,?,?,?,?)
|
|
|
+ // - this weird syntax gives best number for TFB /rawupdates?queries=20 but
|
|
|
+ // seems not good for smaller or higher count - we won't include it in the
|
|
|
+ // ORM but only for our RAW results - as other frameworks (e.g. ntex) do
|
|
|
+ LastComputeUpdateSqlCnt := cnt;
|
|
|
+ W := TTextWriter.CreateOwnedStream(tmp);
|
|
|
+ try
|
|
|
+ W.AddShort('UPDATE world SET randomnumber = CASE id');
|
|
|
+ for i := 1 to cnt do
|
|
|
+ W.AddShort(' when ? then ?');
|
|
|
+ W.AddShort(' else randomNumber end where id in (');
|
|
|
+ repeat
|
|
|
+ W.Add('?', ',');
|
|
|
+ dec(cnt);
|
|
|
+ until cnt = 0;
|
|
|
+ W.CancelLastComma;
|
|
|
+ W.Add(')');
|
|
|
+ W.SetText(LastComputeUpdateSql);
|
|
|
+ finally
|
|
|
+ W.Free;
|
|
|
end;
|
|
|
- result := b.Send;
|
|
|
- if result = HTTP_SUCCESS then
|
|
|
- ctxt.SetOutJson(@res, TypeInfo(TOrmWorlds));
|
|
|
- finally
|
|
|
- b.Free;
|
|
|
- ObjArrayClear(res);
|
|
|
end;
|
|
|
+ result := LastComputeUpdateSql;
|
|
|
+ LastComputeUpdateSqlSafe.UnLock;
|
|
|
end;
|
|
|
|
|
|
function TRawAsyncServer.rawupdates(ctxt: THttpServerRequest): cardinal;
|
|
|
var
|
|
|
cnt, i: PtrInt;
|
|
|
- words: TWorlds;
|
|
|
+ res: TWorlds;
|
|
|
ids, nums: TInt64DynArray;
|
|
|
conn: TSqlDBConnection;
|
|
|
stmt: ISQLDBStatement;
|
|
@@ -441,23 +477,36 @@ begin
|
|
|
result := HTTP_SERVERERROR;
|
|
|
conn := fDbPool.ThreadSafeConnection;
|
|
|
cnt := getQueriesParamValue(ctxt);
|
|
|
- if not getRawRandomWorlds(cnt, words) then
|
|
|
+ if not getRawRandomWorlds(cnt, res) then
|
|
|
exit;
|
|
|
- setLength(ids{%H-}, cnt);
|
|
|
- setLength(nums{%H-}, cnt);
|
|
|
- // generate new randoms, fill parameters arrays for update
|
|
|
- for i := 0 to cnt - 1 do
|
|
|
+ if cnt > 20 then
|
|
|
+ begin
|
|
|
+ setLength(ids{%H-}, cnt);
|
|
|
+ setLength(nums{%H-}, cnt);
|
|
|
+ // generate new randoms, fill parameters arrays for update
|
|
|
+ for i := 0 to cnt - 1 do
|
|
|
+ begin
|
|
|
+ res[i].randomNumber := ComputeRandomWorld;
|
|
|
+ ids[i] := res[i].id;
|
|
|
+ nums[i] := res[i].randomNumber;
|
|
|
+ end;
|
|
|
+ stmt := conn.NewStatementPrepared(WORLD_UPDATE_SQLN, false, true);
|
|
|
+ stmt.BindArray(1, ids);
|
|
|
+ stmt.BindArray(2, nums);
|
|
|
+ end
|
|
|
+ else
|
|
|
begin
|
|
|
- words[i].randomNumber := RandomWorld;
|
|
|
- ids[i] := words[i].id;
|
|
|
- nums[i] := words[i].randomNumber;
|
|
|
+ stmt := conn.NewStatementPrepared(ComputeUpdateSql(cnt), false, true);
|
|
|
+ for i := 0 to cnt - 1 do
|
|
|
+ begin
|
|
|
+ res[i].randomNumber := ComputeRandomWorld;
|
|
|
+ stmt.Bind(i * 2 + 1, res[i].id);
|
|
|
+ stmt.Bind(i * 2 + 2, res[i].randomNumber);
|
|
|
+ stmt.Bind(cnt * 2 + i + 1, res[i].id);
|
|
|
+ end;
|
|
|
end;
|
|
|
- stmt := conn.NewStatementPrepared(WORLD_UPDATE_SQLN, false, true);
|
|
|
- stmt.BindArray(1, ids);
|
|
|
- stmt.BindArray(2, nums);
|
|
|
stmt.ExecutePrepared;
|
|
|
- //conn.Commit; // autocommit
|
|
|
- ctxt.SetOutJson(@words, TypeInfo(TWorlds));
|
|
|
+ ctxt.SetOutJson(@res, TypeInfo(TWorlds));
|
|
|
result := HTTP_SUCCESS;
|
|
|
end;
|
|
|
|
|
@@ -465,74 +514,85 @@ end;
|
|
|
|
|
|
var
|
|
|
rawServers: array of TRawAsyncServer;
|
|
|
- threads, cores, servers, i: integer;
|
|
|
+ threads, servers, i: integer;
|
|
|
flags: THttpServerOptions;
|
|
|
|
|
|
+procedure ComputeExecutionContextFromParams;
|
|
|
+var
|
|
|
+ cores: integer;
|
|
|
+begin
|
|
|
+ // user specified some values at command line: raw [threads] [cores] [servers]
|
|
|
+ // in practice, [cores] is just ignored
|
|
|
+ if not TryStrToInt(ParamStr(1), threads) then
|
|
|
+ threads := SystemInfo.dwNumberOfProcessors * 4;
|
|
|
+ if not TryStrToInt(ParamStr(2), cores) then
|
|
|
+ cores := 16;
|
|
|
+ if not TryStrToInt(ParamStr(3), servers) then
|
|
|
+ servers := 1;
|
|
|
+ if threads < 2 then
|
|
|
+ threads := 2
|
|
|
+ else if threads > 256 then
|
|
|
+ threads := 256; // max. threads for THttpAsyncServer
|
|
|
+ {if SystemInfo.dwNumberOfProcessors > cores then
|
|
|
+ SystemInfo.dwNumberOfProcessors := cores; // for hsoThreadCpuAffinity}
|
|
|
+ if servers < 1 then
|
|
|
+ servers := 1
|
|
|
+ else if servers > 256 then
|
|
|
+ servers := 256;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure ComputeExecutionContextFromNumberOfProcessors;
|
|
|
+var
|
|
|
+ logicalcores: integer;
|
|
|
begin
|
|
|
+ // automatically guess best parameters depending on available CPU cores
|
|
|
+ logicalcores := SystemInfo.dwNumberOfProcessors;
|
|
|
+ if logicalcores >= 12 then
|
|
|
+ begin
|
|
|
+ // high-end CPU - scale using several listeners (one per core)
|
|
|
+ // see https://synopse.info/forum/viewtopic.php?pid=39263#p39263
|
|
|
+ servers := logicalcores;
|
|
|
+ threads := 8;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ // regular CPU - a single instance and a few threads per core
|
|
|
+ servers := 1;
|
|
|
+ threads := logicalcores * 4;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+begin
|
|
|
+ // setup logs
|
|
|
{$ifdef WITH_LOGS}
|
|
|
TSynLog.Family.Level := LOG_VERBOSE; // disable logs for benchmarking
|
|
|
TSynLog.Family.HighResolutionTimestamp := true;
|
|
|
+ TSynLog.Family.PerThreadLog := ptIdentifiedInOneFile;
|
|
|
TSynLog.Family.AutoFlushTimeOut := 1;
|
|
|
{$else}
|
|
|
SynDBLog := nil; // slightly faster: no need to check log level
|
|
|
{$endif WITH_LOGS}
|
|
|
- TSynLog.Family.PerThreadLog := ptIdentifiedInOneFile;
|
|
|
|
|
|
+ // register some RTTI for records JSON serialization
|
|
|
Rtti.RegisterFromText([
|
|
|
TypeInfo(TMessageRec), 'message:RawUtf8',
|
|
|
TypeInfo(TWorldRec), 'id,randomNumber:integer',
|
|
|
TypeInfo(TFortune), 'id:integer message:RawUtf8']);
|
|
|
|
|
|
- flags := [];
|
|
|
+ // setup execution context
|
|
|
if ParamCount > 1 then
|
|
|
- begin
|
|
|
- // user specified some values at command line
|
|
|
- if not TryStrToInt(ParamStr(1), threads) then
|
|
|
- threads := SystemInfo.dwNumberOfProcessors * 4;
|
|
|
- if threads < 2 then
|
|
|
- threads := 2
|
|
|
- else if threads > 256 then
|
|
|
- threads := 256; // max. threads for THttpAsyncServer
|
|
|
-
|
|
|
- if not TryStrToInt(ParamStr(2), cores) then
|
|
|
- cores := 16;
|
|
|
- if SystemInfo.dwNumberOfProcessors > cores then
|
|
|
- SystemInfo.dwNumberOfProcessors := cores; //for hsoThreadCpuAffinity
|
|
|
-
|
|
|
- if not TryStrToInt(ParamStr(3), servers) then
|
|
|
- servers := 1;
|
|
|
- if servers < 1 then
|
|
|
- servers := 1
|
|
|
- else if servers > 16 then
|
|
|
- servers := 16;
|
|
|
- end
|
|
|
+ ComputeExecutionContextFromParams
|
|
|
else
|
|
|
- begin
|
|
|
- // automatically sets best parameters depending on available CPU cores
|
|
|
- cores := SystemInfo.dwNumberOfProcessors;
|
|
|
- if cores > 12 then
|
|
|
- begin
|
|
|
- // hi-end CPU - scale using several listeners bound to the HW cores
|
|
|
- threads := cores;
|
|
|
- if cores div 4 > 6 then
|
|
|
- servers := 6
|
|
|
- else
|
|
|
- servers := cores div 4;
|
|
|
- end
|
|
|
- else
|
|
|
- begin
|
|
|
- threads := cores * 4;
|
|
|
- servers := 1;
|
|
|
- end;
|
|
|
- end;
|
|
|
- if servers = 1 then
|
|
|
- include(flags, hsoThreadSmooting); // 30% better /plaintext e.g. on i5 7300U
|
|
|
+ ComputeExecutionContextFromNumberOfProcessors;
|
|
|
+ if servers > 1 then
|
|
|
+ include(flags, hsoReusePort); // allow several bindings on the same port
|
|
|
|
|
|
- // start the server instance(s), in hsoReusePort mode
|
|
|
+ // start the server instance(s), in hsoReusePort mode if needed
|
|
|
SetLength(rawServers, servers);
|
|
|
for i := 0 to servers - 1 do
|
|
|
rawServers[i] := TRawAsyncServer.Create(threads, flags);
|
|
|
try
|
|
|
+ // display some information and wait for SIGTERM
|
|
|
{$I-}
|
|
|
writeln;
|
|
|
writeln(rawServers[0].fHttpServer.ClassName,
|
|
@@ -542,16 +602,17 @@ begin
|
|
|
', num servers=', servers,
|
|
|
', total workers=', threads * servers,
|
|
|
', db=', rawServers[0].fDbPool.DbmsEngineName);
|
|
|
- writeln('Press Ctrl+C or use SIGTERM to terminate'#10);
|
|
|
- FpPause; // mandatory for the actual benchmark tool
|
|
|
+ writeln(' options=', GetSetName(TypeInfo(THttpServerOptions), flags));
|
|
|
+ writeln('Press [Enter] or Ctrl+C or send SIGTERM to terminate'#10);
|
|
|
+ ConsoleWaitForEnterKey;
|
|
|
//TSynLog.Family.Level := LOG_VERBOSE; // enable shutdown logs for debug
|
|
|
for i := 0 to servers - 1 do
|
|
|
writeln(ObjectToJsonDebug(rawServers[i].fHttpServer,
|
|
|
[woDontStoreVoid, woHumanReadable]));
|
|
|
finally
|
|
|
+ // clear all server instance(s)
|
|
|
ObjArrayClear(rawServers);
|
|
|
end;
|
|
|
-
|
|
|
{$ifdef FPC_X64MM}
|
|
|
WriteHeapStatus(' ', 16, 8, {compileflags=}true);
|
|
|
{$endif FPC_X64MM}
|