agora inbox for [email protected]help / color / mirror / Atom feed
Re: Flexible configuration for full-text search 265+ messages / 3 participants [nested] [flat]
* Re: Flexible configuration for full-text search @ 2018-04-05 08:12 Aleksandr Parfenov <[email protected]> 0 siblings, 1 reply; 265+ messages in thread From: Aleksandr Parfenov @ 2018-04-05 08:12 UTC (permalink / raw) To: Aleksander Alekseev <[email protected]>; +Cc: [email protected] On Fri, 30 Mar 2018 14:43:30 +0000 Aleksander Alekseev <[email protected]> wrote: > The following review has been posted through the commitfest > application: make installcheck-world: tested, passed > Implements feature: tested, passed > Spec compliant: tested, passed > Documentation: tested, passed > > LGTM. > > The new status of this patch is: Ready for Committer It seems that after d204ef6 (MERGE SQL Command) in master the patch doesn't apply due to a conflict in keywords lists (grammar and header). The new version of the patch without conflicts is attached. -- Aleksandr Parfenov Postgres Professional: http://www.postgrespro.com Russian Postgres Company Attachments: [text/x-patch] 0001-flexible-fts-configuration-v10.patch (176.0K, ../../20180405111256.3e5fe4a6@asp437-24-g082ur/2-0001-flexible-fts-configuration-v10.patch) download | inline diff: diff --git a/contrib/unaccent/expected/unaccent.out b/contrib/unaccent/expected/unaccent.out index b93105e..37b9337 100644 --- a/contrib/unaccent/expected/unaccent.out +++ b/contrib/unaccent/expected/unaccent.out @@ -61,3 +61,14 @@ SELECT ts_lexize('unaccent', ' {����} (1 row) +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; +SELECT to_tsvector('unaccent', 'foobar ����� ����'); + to_tsvector +------------------------------ + 'foobar':1 '�����':2 '���':3 +(1 row) + diff --git a/contrib/unaccent/sql/unaccent.sql b/contrib/unaccent/sql/unaccent.sql index 3102139..6ce21cd 100644 --- a/contrib/unaccent/sql/unaccent.sql +++ b/contrib/unaccent/sql/unaccent.sql @@ -2,7 +2,6 @@ CREATE EXTENSION unaccent; -- must have a UTF8 database SELECT getdatabaseencoding(); - SET client_encoding TO 'KOI8'; SELECT unaccent('foobar'); @@ -16,3 +15,12 @@ SELECT unaccent('unaccent', ' SELECT ts_lexize('unaccent', 'foobar'); SELECT ts_lexize('unaccent', '����'); SELECT ts_lexize('unaccent', '����'); + +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); + +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; + +SELECT to_tsvector('unaccent', 'foobar ����� ����'); diff --git a/doc/src/sgml/ref/alter_tsconfig.sgml b/doc/src/sgml/ref/alter_tsconfig.sgml index ebe0b94..ecc3704 100644 --- a/doc/src/sgml/ref/alter_tsconfig.sgml +++ b/doc/src/sgml/ref/alter_tsconfig.sgml @@ -22,8 +22,12 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ALTER MAPPING REPLACE <replaceable class="parameter">old_dictionary</replaceable> WITH <replaceable class="parameter">new_dictionary</replaceable> @@ -89,6 +93,17 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </varlistentry> <varlistentry> + <term><replaceable class="parameter">config</replaceable></term> + <listitem> + <para> + The dictionaries tree expression. The dictionary expression + is a triple of condition/command/else that define way to process + the text. The <literal>ELSE</literal> part is optional. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable class="parameter">old_dictionary</replaceable></term> <listitem> <para> @@ -133,7 +148,7 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </para> </listitem> </varlistentry> - </variablelist> + </variablelist> <para> The <literal>ADD MAPPING FOR</literal> form installs a list of dictionaries to be @@ -155,6 +170,53 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </refsect1> <refsect1> + <title>Dictionaries Map Configuration</title> + + <refsect2> + <title>Format</title> + <para> + Formally <replaceable class="parameter">config</replaceable> is one of: + </para> + <programlisting> + * dictionary_name + + * config { UNION | INTERSECT | EXCEPT | MAP } config + + * CASE config + WHEN [ NO ] MATCH THEN { KEEP | config } + [ ELSE config ] + END + </programlisting> + </refsect2> + + <refsect2> + <title>Description</title> + <para> + <replaceable class="parameter">config</replaceable> can be used + in three different formats. The most simple format is name of dictionary to + use for tokens processing. + </para> + <para> + In order to use more than one dictionary + simultaneously user should interconnect dictionaries by operators. Operators + <literal>UNION</literal>, <literal>EXCEPT</literal> and + <literal>INTERSECT</literal> have same meaning as in operations on sets. + Special operator <literal>MAP</literal> gets output of left subexpression + and uses it as an input to right subexpression. + </para> + <para> + The third format of <replaceable class="parameter">config</replaceable> is similar to + <literal>CASE/WHEN/THEN/ELSE</literal> structure. It's consists of three + replaceable parts. First one is configuration which is used to construct lexemes set + for matching condition. If the condition is triggered, the command is executed. + Use command <literal>KEEP</literal> to avoid repeating of the same + configuration in condition and command part. However, command may differ from + the condition. The <literal>ELSE</literal> branch is executed otherwise. + </para> + </refsect2> + </refsect1> + + <refsect1> <title>Examples</title> <para> @@ -167,6 +229,34 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl ALTER TEXT SEARCH CONFIGURATION my_config ALTER MAPPING REPLACE english WITH swedish; </programlisting> + + <para> + Next example shows how to analyse documents in both English and German languages. + <literal>english_hunspell</literal> and <literal>german_hunspell</literal> + return result only if a word is recognized. Otherwise, stemmer dictionaries + are used to process a token. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + In order to combine search for both exact and processed forms the vector + should contain lexemes produced by <literal>simple</literal> for exact form + of the word as well as lexemes produced by linguistic-aware dictionary + (e.g. <literal>english_stem</literal>) for processed forms. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + </refsect1> <refsect1> diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 610b7bf..1253b41 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -732,10 +732,11 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); The <function>to_tsvector</function> function internally calls a parser which breaks the document text into tokens and assigns a type to each token. For each token, a list of - dictionaries (<xref linkend="textsearch-dictionaries"/>) is consulted, - where the list can vary depending on the token type. The first dictionary - that <firstterm>recognizes</firstterm> the token emits one or more normalized - <firstterm>lexemes</firstterm> to represent the token. For example, + condition/command pairs is consulted, where the list can vary depending + on the token type, condition and command are expressions on dictionaries + with matching clause in condition(<xref linkend="textsearch-dictionaries"/>). + The first command combined with true-resulted condition emits one or more normalized + <firstterm>lexemes</firstterm> to represent the token. For example, <literal>rats</literal> became <literal>rat</literal> because one of the dictionaries recognized that the word <literal>rats</literal> is a plural form of <literal>rat</literal>. Some words are recognized as @@ -743,7 +744,7 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); causes them to be ignored since they occur too frequently to be useful in searching. In our example these are <literal>a</literal>, <literal>on</literal>, and <literal>it</literal>. - If no dictionary in the list recognizes the token then it is also ignored. + If none of conditions is <literal>true</literal> the token is ignored. In this example that happened to the punctuation sign <literal>-</literal> because there are in fact no dictionaries assigned for its token type (<literal>Space symbols</literal>), meaning space tokens will never be @@ -2232,8 +2233,8 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h <para> a single lexeme with the <literal>TSL_FILTER</literal> flag set, to replace the original token with a new token to be passed to subsequent - dictionaries (a dictionary that does this is called a - <firstterm>filtering dictionary</firstterm>) + dictionaries in a comma-separated syntax (a dictionary that does this + is called a <firstterm>filtering dictionary</firstterm>) </para> </listitem> <listitem> @@ -2265,38 +2266,126 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h type that the parser can return, a separate list of dictionaries is specified by the configuration. When a token of that type is found by the parser, each dictionary in the list is consulted in turn, - until some dictionary recognizes it as a known word. If it is identified - as a stop word, or if no dictionary recognizes the token, it will be - discarded and not indexed or searched for. - Normally, the first dictionary that returns a non-<literal>NULL</literal> - output determines the result, and any remaining dictionaries are not - consulted; but a filtering dictionary can replace the given word - with a modified word, which is then passed to subsequent dictionaries. + until command is not selected based on its condition. If none of cases is + selected token will be discarded and not indexed or searched for. </para> <para> - The general rule for configuring a list of dictionaries - is to place first the most narrow, most specific dictionary, then the more - general dictionaries, finishing with a very general dictionary, like + A tree of cases is described as condition/command/else triples. Each + condition is evaluated in order to select appropriate command to generate + resulted set of lexemes. + </para> + + <para> + A condition is an expression with dictionaries used as operands and + basic set operators <literal>UNION</literal>, <literal>EXCEPT</literal>, <literal>INTERSECT</literal> + and special operator <literal>MAP</literal>. + Special operator <literal>MAP</literal> use output of left subexpression as + input for right subexpression. + </para> + + <para> + Rules to write command are same as for condition with additional keyword + <literal>KEEP</literal> considered to use the result of the condition as an output. + </para> + + <para> + A comma-separated list of dictionaries is a simplified variant of text + search configuration. Each dictionary consulted to process a token and first + non-<literal>NULL</literal> output is accepted as a processing result. + </para> + + <para> + The general rule for configuring tokens processing + is to place first case with the most narrow, most specific dictionary, then the more + general dictionaries, finishing with a very general dictionaries, like a <application>Snowball</application> stemmer or <literal>simple</literal>, which - recognizes everything. For example, for an astronomy-specific search + recognizes everything. For example, for an astronomy-specific search (<literal>astro_en</literal> configuration) one could bind token type <type>asciiword</type> (ASCII word) to a synonym dictionary of astronomical terms, a general English dictionary and a <application>Snowball</application> English - stemmer: + stemmer in comma-separated variant of mapping: + </para> <programlisting> ALTER TEXT SEARCH CONFIGURATION astro_en ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem; </programlisting> + + <para> + Another example is a configuration for both English and German languages via + operator-separated variant of mapping: + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION multi_en_de + ADD MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + This configuration provides an ability to search on collection of multilingual + documents without specifying language: + </para> + +<programlisting> +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'lack'); + id | txt +----+----------------------------------------- + 2 | with old stars and lacking gas and dust + +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'beginnen'); + id | txt +----+------------------------------------- + 1 | Das geschah zu Beginn dieses Monats +</programlisting> + + <para> + A combination of stemmer dictionary with <literal>simple</literal> one may be used to mix + search for exact form of one word and linguistic search for others. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION exact_and_linguistic + ADD MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + + <para> + In the following example a simple dictionary is used to prevent words from normalization in query. </para> +<programlisting> +WITH docs(id, txt) as (values (1, 'Supernova star'), + (2, 'Supernova stars')) +SELECT * FROM docs WHERE to_tsvector('exact_and_linguistic', txt) @@ (to_tsquery('simple', 'stars') && to_tsquery('english', 'supernovae')); + id | txt +----+----------------- + 2 | Supernova stars +</programlisting> + + <caution> + <para> + Due to lack of information about origin of each lexeme in <literal>tsvector</literal> may + lead to false-positive triggers in case of stemmed form being used as exact form in a query. + </para> + </caution> + <para> - A filtering dictionary can be placed anywhere in the list, except at the - end where it'd be useless. Filtering dictionaries are useful to partially + Filtering dictionaries are useful to partially normalize words to simplify the task of later dictionaries. For example, a filtering dictionary could be used to remove accents from accented letters, as is done by the <xref linkend="unaccent"/> module. + Filter dictionary should be placed at left of <literal>MAP</literal> + operator. If filter dictionary returns <literal>NULL</literal> it pass initial token + to the right subexpression. </para> <sect2 id="textsearch-stopwords"> @@ -2463,9 +2552,9 @@ SELECT ts_lexize('public.simple_dict','The'); <screen> SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | english_stem | {pari} CREATE TEXT SEARCH DICTIONARY my_synonym ( TEMPLATE = synonym, @@ -2477,9 +2566,12 @@ ALTER TEXT SEARCH CONFIGURATION english WITH my_synonym, english_stem; SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+---------------------------+------------+--------- - asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+---------------------------+---------------------------------------------+------------+--------- + asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | CASE my_synonym WHEN MATCH THEN KEEP +| my_synonym | {paris} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> </para> @@ -3108,6 +3200,21 @@ CREATE TEXT SEARCH DICTIONARY english_ispell ( ALTER TEXT SEARCH CONFIGURATION pg ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part + WITH + CASE pg_dict WHEN MATCH THEN KEEP + ELSE + CASE english_ispell WHEN MATCH THEN KEEP + ELSE english_stem + END + END; +</programlisting> + + Or use alternative comma-separated syntax: + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION pg + ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, + word, hword, hword_part WITH pg_dict, english_ispell, english_stem; </programlisting> @@ -3183,7 +3290,8 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re OUT <replaceable class="parameter">description</replaceable> <type>text</type>, OUT <replaceable class="parameter">token</replaceable> <type>text</type>, OUT <replaceable class="parameter">dictionaries</replaceable> <type>regdictionary[]</type>, - OUT <replaceable class="parameter">dictionary</replaceable> <type>regdictionary</type>, + OUT <replaceable class="parameter">configuration</replaceable> <type>text</type>, + OUT <replaceable class="parameter">command</replaceable> <type>text</type>, OUT <replaceable class="parameter">lexemes</replaceable> <type>text[]</type>) returns setof record </synopsis> @@ -3227,14 +3335,20 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re </listitem> <listitem> <para> - <replaceable>dictionary</replaceable> <type>regdictionary</type> — the dictionary - that recognized the token, or <literal>NULL</literal> if none did + <replaceable>configuration</replaceable> <type>text</type> — the + configuration defined for this token type + </para> + </listitem> + <listitem> + <para> + <replaceable>command</replaceable> <type>text</type> — the command that describes + the way the output was produced </para> </listitem> <listitem> <para> <replaceable>lexemes</replaceable> <type>text[]</type> — the lexeme(s) produced - by the dictionary that recognized the token, or <literal>NULL</literal> if + by the command selected according conditions, or <literal>NULL</literal> if none did; an empty array (<literal>{}</literal>) means it was recognized as a stop word </para> @@ -3247,32 +3361,32 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re <screen> SELECT * FROM ts_debug('english','a fat cat sat on a mat - it ate a fat rats'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | cat | {english_stem} | english_stem | {cat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | sat | {english_stem} | english_stem | {sat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | on | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | mat | {english_stem} | english_stem | {mat} - blank | Space symbols | | {} | | - blank | Space symbols | - | {} | | - asciiword | Word, all ASCII | it | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | ate | {english_stem} | english_stem | {ate} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | rats | {english_stem} | english_stem | {rat} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | cat | {english_stem} | english_stem | english_stem | {cat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | sat | {english_stem} | english_stem | english_stem | {sat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | on | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | mat | {english_stem} | english_stem | english_stem | {mat} + blank | Space symbols | | | | | + blank | Space symbols | - | | | | + asciiword | Word, all ASCII | it | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | ate | {english_stem} | english_stem | english_stem | {ate} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | rats | {english_stem} | english_stem | english_stem | {rat} </screen> </para> @@ -3298,13 +3412,22 @@ ALTER TEXT SEARCH CONFIGURATION public.english <screen> SELECT * FROM ts_debug('public.english','The Brightest supernovaes'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------------+-------------------------------+----------------+------------- - asciiword | Word, all ASCII | The | {english_ispell,english_stem} | english_ispell | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | english_ispell | {bright} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | english_stem | {supernova} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------------+-------------------------------+---------------------------------------------+------------------+------------- + asciiword | Word, all ASCII | The | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {bright} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_stem | {supernova} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> <para> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e9e1886..34b80ae 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -948,55 +948,14 @@ GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublicatio -- Tsearch debug function. Defined here because it'd be pretty unwieldy -- to put it into pg_proc.h -CREATE FUNCTION ts_debug(IN config regconfig, IN document text, - OUT alias text, - OUT description text, - OUT token text, - OUT dictionaries regdictionary[], - OUT dictionary regdictionary, - OUT lexemes text[]) -RETURNS SETOF record AS -$$ -SELECT - tt.alias AS alias, - tt.description AS description, - parse.token AS token, - ARRAY ( SELECT m.mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY m.mapseqno ) - AS dictionaries, - ( SELECT mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS dictionary, - ( SELECT pg_catalog.ts_lexize(mapdict, parse.token) - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS lexemes -FROM pg_catalog.ts_parse( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ), $2 - ) AS parse, - pg_catalog.ts_token_type( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ) - ) AS tt -WHERE tt.tokid = parse.tokid -$$ -LANGUAGE SQL STRICT STABLE PARALLEL SAFE; - -COMMENT ON FUNCTION ts_debug(regconfig,text) IS - 'debug function for text search configuration'; CREATE FUNCTION ts_debug(IN document text, OUT alias text, OUT description text, OUT token text, OUT dictionaries regdictionary[], - OUT dictionary regdictionary, + OUT configuration text, + OUT command text, OUT lexemes text[]) RETURNS SETOF record AS $$ diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 3a84351..53ee576 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -39,9 +39,12 @@ #include "nodes/makefuncs.h" #include "parser/parse_func.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_public.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" @@ -935,11 +938,22 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + TSMapElement *mapdicts = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + Oid *dictionaryOids = TSMapGetDictionaries(mapdicts); + Oid *currentOid = dictionaryOids; - referenced.classId = TSDictionaryRelationId; - referenced.objectId = cfgmap->mapdict; - referenced.objectSubId = 0; - add_exact_object_address(&referenced, addrs); + while (*currentOid != InvalidOid) + { + referenced.classId = TSDictionaryRelationId; + referenced.objectId = *currentOid; + referenced.objectSubId = 0; + add_exact_object_address(&referenced, addrs); + + currentOid++; + } + + pfree(dictionaryOids); + TSMapElementFree(mapdicts); } systable_endscan(scan); @@ -1091,8 +1105,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied) mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid; mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype; - mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno; - mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict; + mapvalues[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(&cfgmap->mapdicts); newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls); @@ -1195,7 +1208,7 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt) relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock); /* Add or drop mappings */ - if (stmt->dicts) + if (stmt->dicts || stmt->dict_map) MakeConfigurationMapping(stmt, tup, relMap); else if (stmt->tokentype) DropConfigurationMapping(stmt, tup, relMap); @@ -1271,6 +1284,59 @@ getTokenTypes(Oid prsId, List *tokennames) } /* + * Parse parse node extracted from dictionary mapping and transform it into + * internal representation of dictionary mapping. + */ +static TSMapElement * +ParseTSMapConfig(DictMapElem *elem) +{ + TSMapElement *result = palloc0(sizeof(TSMapElement)); + + if (elem->kind == DICT_MAP_CASE) + { + TSMapCase *caseObject = palloc0(sizeof(TSMapCase)); + DictMapCase *caseASTObject = elem->data; + + caseObject->condition = ParseTSMapConfig(caseASTObject->condition); + caseObject->command = ParseTSMapConfig(caseASTObject->command); + + if (caseASTObject->elsebranch) + caseObject->elsebranch = ParseTSMapConfig(caseASTObject->elsebranch); + + caseObject->match = caseASTObject->match; + + caseObject->condition->parent = result; + caseObject->command->parent = result; + + result->type = TSMAP_CASE; + result->value.objectCase = caseObject; + } + else if (elem->kind == DICT_MAP_EXPRESSION) + { + TSMapExpression *expression = palloc0(sizeof(TSMapExpression)); + DictMapExprElem *expressionAST = elem->data; + + expression->left = ParseTSMapConfig(expressionAST->left); + expression->right = ParseTSMapConfig(expressionAST->right); + expression->operator = expressionAST->oper; + + result->type = TSMAP_EXPRESSION; + result->value.objectExpression = expression; + } + else if (elem->kind == DICT_MAP_KEEP) + { + result->value.objectExpression = NULL; + result->type = TSMAP_KEEP; + } + else if (elem->kind == DICT_MAP_DICTIONARY) + { + result->value.objectDictionary = get_ts_dict_oid(elem->data, false); + result->type = TSMAP_DICTIONARY; + } + return result; +} + +/* * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING */ static void @@ -1286,8 +1352,9 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, Oid prsId; int *tokens, ntoken; - Oid *dictIds; - int ndict; + Oid *dictIds = NULL; + int ndict = 0; + TSMapElement *config = NULL; ListCell *c; prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser; @@ -1326,15 +1393,18 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Convert list of dictionary names to array of dict OIDs */ - ndict = list_length(stmt->dicts); - dictIds = (Oid *) palloc(sizeof(Oid) * ndict); - i = 0; - foreach(c, stmt->dicts) + if (stmt->dicts) { - List *names = (List *) lfirst(c); + ndict = list_length(stmt->dicts); + dictIds = (Oid *) palloc(sizeof(Oid) * ndict); + i = 0; + foreach(c, stmt->dicts) + { + List *names = (List *) lfirst(c); - dictIds[i] = get_ts_dict_oid(names, false); - i++; + dictIds[i] = get_ts_dict_oid(names, false); + i++; + } } if (stmt->replace) @@ -1356,6 +1426,10 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + Datum repl_val[Natts_pg_ts_config_map]; + bool repl_null[Natts_pg_ts_config_map]; + bool repl_repl[Natts_pg_ts_config_map]; + HeapTuple newtup; /* * check if it's one of target token types @@ -1379,25 +1453,21 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * replace dictionary if match */ - if (cfgmap->mapdict == dictOld) - { - Datum repl_val[Natts_pg_ts_config_map]; - bool repl_null[Natts_pg_ts_config_map]; - bool repl_repl[Natts_pg_ts_config_map]; - HeapTuple newtup; - - memset(repl_val, 0, sizeof(repl_val)); - memset(repl_null, false, sizeof(repl_null)); - memset(repl_repl, false, sizeof(repl_repl)); - - repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew); - repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true; - - newtup = heap_modify_tuple(maptup, - RelationGetDescr(relMap), - repl_val, repl_null, repl_repl); - CatalogTupleUpdate(relMap, &newtup->t_self, newtup); - } + config = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + TSMapReplaceDictionary(config, dictOld, dictNew); + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + repl_val[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); + repl_repl[Anum_pg_ts_config_map_mapdicts - 1] = true; + + newtup = heap_modify_tuple(maptup, + RelationGetDescr(relMap), + repl_val, repl_null, repl_repl); + CatalogTupleUpdate(relMap, &newtup->t_self, newtup); + pfree(config); } systable_endscan(scan); @@ -1407,24 +1477,22 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Insertion of new entries */ + config = ParseTSMapConfig(stmt->dict_map); + for (i = 0; i < ntoken; i++) { - for (j = 0; j < ndict; j++) - { - Datum values[Natts_pg_ts_config_map]; - bool nulls[Natts_pg_ts_config_map]; + Datum values[Natts_pg_ts_config_map]; + bool nulls[Natts_pg_ts_config_map]; - memset(nulls, false, sizeof(nulls)); - values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); - values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); - values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1); - values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]); + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); + values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); + values[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); - tup = heap_form_tuple(relMap->rd_att, values, nulls); - CatalogTupleInsert(relMap, tup); + tup = heap_form_tuple(relMap->rd_att, values, nulls); + CatalogTupleInsert(relMap, tup); - heap_freetuple(tup); - } + heap_freetuple(tup); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c3efca3..a2235c3 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4439,6 +4439,42 @@ _copyReassignOwnedStmt(const ReassignOwnedStmt *from) return newnode; } +static DictMapElem * +_copyDictMapElem(const DictMapElem *from) +{ + DictMapElem *newnode = makeNode(DictMapElem); + + COPY_SCALAR_FIELD(kind); + COPY_NODE_FIELD(data); + + return newnode; +} + +static DictMapExprElem * +_copyDictMapExprElem(const DictMapExprElem *from) +{ + DictMapExprElem *newnode = makeNode(DictMapExprElem); + + COPY_NODE_FIELD(left); + COPY_NODE_FIELD(right); + COPY_SCALAR_FIELD(oper); + + return newnode; +} + +static DictMapCase * +_copyDictMapCase(const DictMapCase *from) +{ + DictMapCase *newnode = makeNode(DictMapCase); + + COPY_NODE_FIELD(condition); + COPY_NODE_FIELD(command); + COPY_NODE_FIELD(elsebranch); + COPY_SCALAR_FIELD(match); + + return newnode; +} + static AlterTSDictionaryStmt * _copyAlterTSDictionaryStmt(const AlterTSDictionaryStmt *from) { @@ -5452,6 +5488,15 @@ copyObjectImpl(const void *from) case T_ReassignOwnedStmt: retval = _copyReassignOwnedStmt(from); break; + case T_DictMapExprElem: + retval = _copyDictMapExprElem(from); + break; + case T_DictMapElem: + retval = _copyDictMapElem(from); + break; + case T_DictMapCase: + retval = _copyDictMapCase(from); + break; case T_AlterTSDictionaryStmt: retval = _copyAlterTSDictionaryStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 45ceba2..71a8f9b 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2218,6 +2218,36 @@ _equalReassignOwnedStmt(const ReassignOwnedStmt *a, const ReassignOwnedStmt *b) } static bool +_equalDictMapElem(const DictMapElem *a, const DictMapElem *b) +{ + COMPARE_NODE_FIELD(data); + COMPARE_SCALAR_FIELD(kind); + + return true; +} + +static bool +_equalDictMapExprElem(const DictMapExprElem *a, const DictMapExprElem *b) +{ + COMPARE_NODE_FIELD(left); + COMPARE_NODE_FIELD(right); + COMPARE_SCALAR_FIELD(oper); + + return true; +} + +static bool +_equalDictMapCase(const DictMapCase *a, const DictMapCase *b) +{ + COMPARE_NODE_FIELD(condition); + COMPARE_NODE_FIELD(command); + COMPARE_NODE_FIELD(elsebranch); + COMPARE_SCALAR_FIELD(match); + + return true; +} + +static bool _equalAlterTSDictionaryStmt(const AlterTSDictionaryStmt *a, const AlterTSDictionaryStmt *b) { COMPARE_NODE_FIELD(dictname); @@ -3575,6 +3605,15 @@ equal(const void *a, const void *b) case T_ReassignOwnedStmt: retval = _equalReassignOwnedStmt(a, b); break; + case T_DictMapExprElem: + retval = _equalDictMapExprElem(a, b); + break; + case T_DictMapElem: + retval = _equalDictMapElem(a, b); + break; + case T_DictMapCase: + retval = _equalDictMapCase(a, b); + break; case T_AlterTSDictionaryStmt: retval = _equalAlterTSDictionaryStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b879358..16a63d3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -52,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config_map.h" #include "commands/defrem.h" #include "commands/trigger.h" #include "nodes/makefuncs.h" @@ -241,6 +242,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PartitionSpec *partspec; PartitionBoundSpec *partboundspec; RoleSpec *rolespec; + DictMapElem *dmapelem; } %type <node> stmt schema_stmt @@ -310,7 +312,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); analyze_option_list analyze_option_elem %type <boolean> opt_or_replace opt_grant_grant_option opt_grant_admin_option - opt_nowait opt_if_exists opt_with_data + opt_nowait opt_if_exists opt_with_data opt_dictionary_map_no %type <ival> opt_nowait_or_skip %type <list> OptRoleList AlterOptRoleList @@ -585,6 +587,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <list> hash_partbound partbound_datum_list range_datum_list %type <defelt> hash_partbound_elem +%type <ival> dictionary_map_set_expr_operator +%type <dmapelem> dictionary_map_dict dictionary_map_command_expr_paren + dictionary_map_set_expr dictionary_map_case + dictionary_map_action opt_dictionary_map_case_else + dictionary_config dictionary_config_comma + %type <node> merge_when_clause opt_and_condition %type <list> merge_when_list %type <node> merge_update merge_delete merge_insert @@ -650,13 +658,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JOIN - KEY + KEEP KEY LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MAP MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE @@ -10355,24 +10363,26 @@ AlterTSDictionaryStmt: ; AlterTSConfigurationStmt: - ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list + ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ADD_MAPPING; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = false; n->replace = false; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list + | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = true; n->replace = false; $$ = (Node*)n; @@ -10424,6 +10434,117 @@ any_with: WITH {} | WITH_LA {} ; +opt_dictionary_map_no: + NO { $$ = true; } + | { $$ = false; } + ; + +dictionary_config_comma: + dictionary_map_dict { $$ = $1; } + | dictionary_map_dict ',' dictionary_config_comma + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = TSMAP_OP_COMMA; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_config: + dictionary_map_set_expr { $$ = $1; } + | dictionary_map_dict ',' dictionary_config_comma + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = TSMAP_OP_COMMA; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_action: + KEEP + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_KEEP; + n->data = NULL; + $$ = n; + } + | dictionary_map_set_expr { $$ = $1; } + ; + +opt_dictionary_map_case_else: + ELSE dictionary_map_set_expr { $$ = $2; } + | { $$ = NULL; } + ; + +dictionary_map_case: + CASE dictionary_map_set_expr WHEN opt_dictionary_map_no MATCH THEN dictionary_map_action opt_dictionary_map_case_else END_P + { + DictMapCase *n = makeNode(DictMapCase); + DictMapElem *r = makeNode(DictMapElem); + + n->condition = $2; + n->command = $7; + n->elsebranch = $8; + n->match = !$4; + + r->kind = DICT_MAP_CASE; + r->data = n; + $$ = r; + } + ; + +dictionary_map_set_expr_operator: + UNION { $$ = TSMAP_OP_UNION; } + | EXCEPT { $$ = TSMAP_OP_EXCEPT; } + | INTERSECT { $$ = TSMAP_OP_INTERSECT; } + | MAP { $$ = TSMAP_OP_MAP; } + ; + +dictionary_map_set_expr: + dictionary_map_command_expr_paren { $$ = $1; } + | dictionary_map_set_expr dictionary_map_set_expr_operator dictionary_map_command_expr_paren + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = $2; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_command_expr_paren: + '(' dictionary_map_set_expr ')' { $$ = $2; } + | dictionary_map_dict { $$ = $1; } + | dictionary_map_case { $$ = $1; } + ; + +dictionary_map_dict: + any_name + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_DICTIONARY; + n->data = $1; + $$ = n; + } + ; /***************************************************************************** * @@ -15241,6 +15362,7 @@ unreserved_keyword: | LOCK_P | LOCKED | LOGGED + | MAP | MAPPING | MATCH | MATCHED @@ -15549,6 +15671,7 @@ reserved_keyword: | INITIALLY | INTERSECT | INTO + | KEEP | LATERAL_P | LEADING | LIMIT diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile index 227468a..e61ad4f 100644 --- a/src/backend/tsearch/Makefile +++ b/src/backend/tsearch/Makefile @@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES)) OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \ dict_simple.o dict_synonym.o dict_thesaurus.o \ dict_ispell.o regis.o spell.o \ - to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o + to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o ts_configmap.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/tsearch/ts_configmap.c b/src/backend/tsearch/ts_configmap.c new file mode 100644 index 0000000..51860ff --- /dev/null +++ b/src/backend/tsearch/ts_configmap.c @@ -0,0 +1,1094 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.c + * internal representation of text search configuration and utilities for it + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/tsearch/ts_confimap.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/indexing.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_namespace.h" +#include "catalog/namespace.h" +#include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +/* + * Size selected arbitrary, based on assumption that 1024 frames of stack + * is enough for parsing of configurations + */ +#define JSONB_PARSE_STATE_STACK_SIZE 1024 + +/* + * Used during the parsing of TSMapElement from JSONB into internal + * data structures. + */ +typedef enum TSMapParseState +{ + TSMPS_WAIT_ELEMENT, + TSMPS_READ_DICT_OID, + TSMPS_READ_COMPLEX_OBJ, + TSMPS_READ_EXPRESSION, + TSMPS_READ_CASE, + TSMPS_READ_OPERATOR, + TSMPS_READ_COMMAND, + TSMPS_READ_CONDITION, + TSMPS_READ_ELSEBRANCH, + TSMPS_READ_MATCH, + TSMPS_READ_KEEP, + TSMPS_READ_LEFT, + TSMPS_READ_RIGHT +} TSMapParseState; + +/* + * Context used during JSONB parsing to construct a TSMap + */ +typedef struct TSMapJsonbParseData +{ + TSMapParseState states[JSONB_PARSE_STATE_STACK_SIZE]; /* Stack of states of + * JSONB parsing + * automaton */ + int statesIndex; /* Index of current stack frame */ + TSMapElement *element; /* Element that is in construction now */ +} TSMapJsonbParseData; + +static JsonbValue *TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState); +static TSMapElement * JsonbToTSMapElement(JsonbContainer *root); + +/* + * Print name of the namespace into StringInfo variable result + */ +static void +TSMapPrintNamespace(Oid namespaceId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_namespace namespace; + + if (false) + return; + + maprel = heap_open(NamespaceRelationId, AccessShareLock); + mapidx = index_open(NamespaceOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + namespace = (Form_pg_namespace) GETSTRUCT(maptup); + appendStringInfoString(result, namespace->nspname.data); + appendStringInfoChar(result, '.'); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print name of the dictionary into StringInfo variable result + */ +void +TSMapPrintDictName(Oid dictId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_ts_dict dict; + + if (false) + return; +maprel = heap_open(TSDictionaryRelationId, AccessShareLock); + mapidx = index_open(TSDictionaryOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dictId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + dict = (Form_pg_ts_dict) GETSTRUCT(maptup); + if (!TSDictionaryIsVisible(dictId)) + { + TSMapPrintNamespace(dict->dictnamespace, result); + } + appendStringInfoString(result, dict->dictname.data); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print the expression into StringInfo variable result + */ +static void +TSMapPrintExpression(TSMapExpression *expression, StringInfo result) +{ + + if (expression->left) + TSMapPrintElement(expression->left, result); + + switch (expression->operator) + { + case TSMAP_OP_UNION: + appendStringInfoString(result, " UNION "); + break; + case TSMAP_OP_EXCEPT: + appendStringInfoString(result, " EXCEPT "); + break; + case TSMAP_OP_INTERSECT: + appendStringInfoString(result, " INTERSECT "); + break; + case TSMAP_OP_COMMA: + appendStringInfoString(result, ", "); + break; + case TSMAP_OP_MAP: + appendStringInfoString(result, " MAP "); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } + + if (expression->right) + TSMapPrintElement(expression->right, result); +} + +/* + * Print the case configuration construction into StringInfo variable result + */ +static void +TSMapPrintCase(TSMapCase *caseObject, StringInfo result) +{ + appendStringInfoString(result, "CASE "); + + TSMapPrintElement(caseObject->condition, result); + + appendStringInfoString(result, " WHEN "); + if (!caseObject->match) + appendStringInfoString(result, "NO "); + appendStringInfoString(result, "MATCH THEN "); + + TSMapPrintElement(caseObject->command, result); + + if (caseObject->elsebranch != NULL) + { + appendStringInfoString(result, "\nELSE "); + TSMapPrintElement(caseObject->elsebranch, result); + } + appendStringInfoString(result, "\nEND"); +} + +/* + * Print the element into StringInfo result. + * Uses other function and serves for element type detection. + */ +void +TSMapPrintElement(TSMapElement *element, StringInfo result) +{ + switch (element->type) + { + case TSMAP_EXPRESSION: + TSMapPrintExpression(element->value.objectExpression, result); + break; + case TSMAP_DICTIONARY: + TSMapPrintDictName(element->value.objectDictionary, result); + break; + case TSMAP_CASE: + TSMapPrintCase(element->value.objectCase, result); + break; + case TSMAP_KEEP: + appendStringInfoString(result, "KEEP"); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains elements with invalid type."))); + break; + } +} + +/* + * Print the text search configuration as a text. + */ +Datum +dictionary_mapping_to_text(PG_FUNCTION_ARGS) +{ + Oid cfgOid = PG_GETARG_OID(0); + int32 tokentype = PG_GETARG_INT32(1); + StringInfo rawResult; + text *result = NULL; + TSConfigCacheEntry *cacheEntry; + + cacheEntry = lookup_ts_config_cache(cfgOid); + rawResult = makeStringInfo(); + initStringInfo(rawResult); + + if (cacheEntry->lenmap > tokentype && cacheEntry->map[tokentype] != NULL) + { + TSMapElement *element = cacheEntry->map[tokentype]; + + TSMapPrintElement(element, rawResult); + } + + result = cstring_to_text(rawResult->data); + pfree(rawResult); + PG_RETURN_TEXT_P(result); +} + +/* ---------------- + * Functions used to convert TSMap structure into JSONB representation + * ---------------- + */ + +/* + * Convert an integer value into JsonbValue + */ +static JsonbValue * +IntToJsonbValue(int intValue) +{ + char buffer[16]; + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + /* + * String size is based on limit of int capacity up to 12 chars with sign + * and NULL-character + */ + memset(buffer, 0, sizeof(char) * 12); + + pg_ltoa(intValue, buffer); + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(buffer), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1) + )); + return value; +} + +/* + * Convert a FTS configuration expression into JsonbValue + */ +static JsonbValue * +TSMapExpressionToJsonbValue(TSMapExpression *expression, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("operator"); + key.val.string.val = "operator"; + value = IntToJsonbValue(expression->operator); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("left"); + key.val.string.val = "left"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->left, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("right"); + key.val.string.val = "right"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->right, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS configuration case into JsonbValue + */ +static JsonbValue * +TSMapCaseToJsonbValue(TSMapCase *caseObject, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("condition"); + key.val.string.val = "condition"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->condition, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("command"); + key.val.string.val = "command"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->command, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + if (caseObject->elsebranch != NULL) + { + key.type = jbvString; + key.val.string.len = strlen("elsebranch"); + key.val.string.val = "elsebranch"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->elsebranch, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + } + + key.type = jbvString; + key.val.string.len = strlen("match"); + key.val.string.val = "match"; + + value = IntToJsonbValue(caseObject->match ? 1 : 0); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS KEEP command into JsonbValue + */ +static JsonbValue * +TSMapKeepToJsonbValue(JsonbParseState *jsonbState) +{ + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + value->type = jbvString; + value->val.string.len = strlen("keep"); + value->val.string.val = "keep"; + + return pushJsonbValue(&jsonbState, WJB_VALUE, value); +} + +/* + * Convert a FTS element into JsonbValue. Common point for all types of TSMapElement + */ +JsonbValue * +TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState) +{ + JsonbValue *result = NULL; + + if (element != NULL) + { + switch (element->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionToJsonbValue(element->value.objectExpression, jsonbState); + break; + case TSMAP_DICTIONARY: + result = IntToJsonbValue(element->value.objectDictionary); + break; + case TSMAP_CASE: + result = TSMapCaseToJsonbValue(element->value.objectCase, jsonbState); + break; + case TSMAP_KEEP: + result = TSMapKeepToJsonbValue(jsonbState); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Required text search configuration contains elements with invalid type."))); + break; + } + } + return result; +} + +/* + * Convert a FTS configuration into JSONB + */ +Jsonb * +TSMapToJsonb(TSMapElement *element) +{ + JsonbParseState *jsonbState = NULL; + JsonbValue *out; + Jsonb *result; + + out = TSMapElementToJsonbValue(element, jsonbState); + + result = JsonbValueToJsonb(out); + return result; +} + +/* ---------------- + * Functions used to get TSMap structure from JSONB representation + * ---------------- + */ + +/* + * Extract an integer from JsonbValue + */ +static int +JsonbValueToInt(JsonbValue *value) +{ + char *str; + + str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(value->val.numeric))); + return pg_atoi(str, sizeof(int), 0); +} + +/* + * Check is a key one of FTS configuration case fields + */ +static bool +IsTSMapCaseKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "match") == 0 || strcmp(key, "condition") == 0 || strcmp(key, "command") == 0 || strcmp(key, "elsebranch") == 0; +} + +/* + * Check is a key one of FTS configuration expression fields + */ +static bool +IsTSMapExpressionKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "operator") == 0 || strcmp(key, "left") == 0 || strcmp(key, "right") == 0; +} + +/* + * Configure parseData->element according to value (key) + */ +static void +JsonbBeginObjectKey(JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *parentElement = parseData->element; + + parseData->element = palloc0(sizeof(TSMapElement)); + parseData->element->parent = parentElement; + + /* Overwrite object-type state based on key */ + if (IsTSMapExpressionKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_EXPRESSION; + parseData->element->type = TSMAP_EXPRESSION; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapExpression)); + } + else if (IsTSMapCaseKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_CASE; + parseData->element->type = TSMAP_CASE; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapCase)); + } +} + +/* + * Process a JsonbValue inside a FTS configuration expression + */ +static void +JsonbKeyExpressionProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "operator") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_OPERATOR; + else if (strcmp(key, "left") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_LEFT; + else if (strcmp(key, "right") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_RIGHT; +} + +/* + * Process a JsonbValue inside a FTS configuration case + */ +static void +JsonbKeyCaseProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "condition") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_CONDITION; + else if (strcmp(key, "command") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_COMMAND; + else if (strcmp(key, "elsebranch") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_ELSEBRANCH; + else if (strcmp(key, "match") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_MATCH; +} + +/* + * Convert a JsonbValue into OID TSMapElement + */ +static TSMapElement * +JsonbValueToOidElement(JsonbValue *value, TSMapElement *parent) +{ + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + element->type = TSMAP_DICTIONARY; + element->value.objectDictionary = JsonbValueToInt(value); + return element; +} + +/* + * Convert a JsonbValue into string TSMapElement. + * Used for special values such as KEEP command + */ +static TSMapElement * +JsonbValueReadString(JsonbValue *value, TSMapElement *parent) +{ + char *str; + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + str = palloc0(sizeof(char) * (value->val.string.len + 1)); + memcpy(str, value->val.string.val, sizeof(char) * value->val.string.len); + + if (strcmp(str, "keep") == 0) + element->type = TSMAP_KEEP; + + pfree(str); + + return element; +} + +/* + * Process a JsonbValue object + */ +static void +JsonbProcessElement(JsonbIteratorToken r, JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *element = NULL; + + switch (r) + { + case WJB_KEY: + + /* + * Construct an TSMapElement object. At first key inside JSONB + * object a type is selected based on key. + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMPLEX_OBJ) + JsonbBeginObjectKey(value, parseData); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_EXPRESSION) + JsonbKeyExpressionProcessing(value, parseData); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CASE) + JsonbKeyCaseProcessing(value, parseData); + + break; + case WJB_BEGIN_OBJECT: + + /* + * Begin construction of new object + */ + parseData->statesIndex++; + parseData->states[parseData->statesIndex] = TSMPS_READ_COMPLEX_OBJ; + break; + case WJB_END_OBJECT: + + /* + * Save constructed object based on current state of parser + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->parent->value.objectExpression->left = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->parent->value.objectExpression->right = parseData->element; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->parent->value.objectCase->condition = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->parent->value.objectCase->command = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->parent->value.objectCase->elsebranch = parseData->element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_VALUE: + + /* + * Save a value inside constructing object + */ + if (value.type == jbvBinary) + element = JsonbToTSMapElement(value.val.binary.data); + else if (value.type == jbvString) + element = JsonbValueReadString(&value, parseData->element); + else if (value.type == jbvNumeric) + element = JsonbValueToOidElement(&value, parseData->element); + else + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->value.objectCase->condition = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->value.objectCase->command = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->value.objectCase->elsebranch = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_MATCH) + parseData->element->value.objectCase->match = JsonbValueToInt(&value) == 1 ? true : false; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_OPERATOR) + parseData->element->value.objectExpression->operator = JsonbValueToInt(&value); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->value.objectExpression->left = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->value.objectExpression->right = element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_ELEM: + + /* + * Store a simple element such as dictionary OID + */ + if (parseData->states[parseData->statesIndex] == TSMPS_WAIT_ELEMENT) + { + if (parseData->element != NULL) + parseData->element = JsonbValueToOidElement(&value, parseData->element->parent); + else + parseData->element = JsonbValueToOidElement(&value, NULL); + } + break; + default: + /* Ignore unused JSONB tokens */ + break; + } +} + +/* + * Convert a JsonbContainer into TSMapElement + */ +static TSMapElement * +JsonbToTSMapElement(JsonbContainer *root) +{ + TSMapJsonbParseData parseData; + JsonbIteratorToken r; + JsonbIterator *it; + JsonbValue val; + + parseData.statesIndex = 0; + parseData.states[parseData.statesIndex] = TSMPS_WAIT_ELEMENT; + parseData.element = NULL; + + it = JsonbIteratorInit(root); + + while ((r = JsonbIteratorNext(&it, &val, true)) != WJB_DONE) + JsonbProcessElement(r, val, &parseData); + + return parseData.element; +} + +/* + * Convert a JSONB into TSMapElement + */ +TSMapElement * +JsonbToTSMap(Jsonb *json) +{ + JsonbContainer *root = &json->root; + + return JsonbToTSMapElement(root); +} + +/* ---------------- + * Text Search Configuration Map Utils + * ---------------- + */ + +/* + * Dynamically extendable list of OIDs + */ +typedef struct OidList +{ + Oid *data; + int size; /* Size of data array. Uninitialized elements + * in data filled with InvalidOid */ +} OidList; + +/* + * Initialize a list + */ +static OidList * +OidListInit() +{ + OidList *result = palloc0(sizeof(OidList)); + + result->size = 1; + result->data = palloc0(result->size * sizeof(Oid)); + result->data[0] = InvalidOid; + return result; +} + +/* + * Add a new OID into list. If it is already stored in list, it won't be add second time. + */ +static void +OidListAdd(OidList *list, Oid oid) +{ + int i; + + /* Search for the Oid in the list */ + for (i = 0; list->data[i] != InvalidOid; i++) + if (list->data[i] == oid) + return; + + /* If not found, insert it in the end of the list */ + if (i >= list->size - 1) + { + int j; + + list->size = list->size * 2; + list->data = repalloc(list->data, sizeof(Oid) * list->size); + + for (j = i; j < list->size; j++) + list->data[j] = InvalidOid; + } + list->data[i] = oid; +} + +/* + * Get OIDs of all dictionaries used in TSMapElement. + * Used for internal recursive calls. + */ +static void +TSMapGetDictionariesInternal(TSMapElement *config, OidList *list) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapGetDictionariesInternal(config->value.objectExpression->left, list); + TSMapGetDictionariesInternal(config->value.objectExpression->right, list); + break; + case TSMAP_CASE: + TSMapGetDictionariesInternal(config->value.objectCase->command, list); + TSMapGetDictionariesInternal(config->value.objectCase->condition, list); + if (config->value.objectCase->elsebranch != NULL) + TSMapGetDictionariesInternal(config->value.objectCase->elsebranch, list); + break; + case TSMAP_DICTIONARY: + OidListAdd(list, config->value.objectDictionary); + break; + } +} + +/* + * Get OIDs of all dictionaries used in TSMapElement + */ +Oid * +TSMapGetDictionaries(TSMapElement *config) +{ + Oid *result; + OidList *list = OidListInit(); + + TSMapGetDictionariesInternal(config, list); + + result = list->data; + pfree(list); + + return result; +} + +/* + * Replace one dictionary OID with another in all instances inside a configuration + */ +void +TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapReplaceDictionary(config->value.objectExpression->left, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectExpression->right, oldDict, newDict); + break; + case TSMAP_CASE: + TSMapReplaceDictionary(config->value.objectCase->command, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectCase->condition, oldDict, newDict); + if (config->value.objectCase->elsebranch != NULL) + TSMapReplaceDictionary(config->value.objectCase->elsebranch, oldDict, newDict); + break; + case TSMAP_DICTIONARY: + if (config->value.objectDictionary == oldDict) + config->value.objectDictionary = newDict; + break; + } +} + +/* ---------------- + * Text Search Configuration Map Memory Management + * ---------------- + */ + +/* + * Move a FTS configuration expression to another memory context + */ +static TSMapElement * +TSMapExpressionMoveToMemoryContext(TSMapExpression *expression, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapExpression *resultExpression = MemoryContextAlloc(context, sizeof(TSMapExpression)); + + memset(resultExpression, 0, sizeof(TSMapExpression)); + result->value.objectExpression = resultExpression; + result->type = TSMAP_EXPRESSION; + + resultExpression->operator = expression->operator; + + resultExpression->left = TSMapMoveToMemoryContext(expression->left, context); + resultExpression->left->parent = result; + + resultExpression->right = TSMapMoveToMemoryContext(expression->right, context); + resultExpression->right->parent = result; + + return result; +} + +/* + * Move a FTS configuration case to another memory context + */ +static TSMapElement * +TSMapCaseMoveToMemoryContext(TSMapCase *caseObject, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapCase *resultCaseObject = MemoryContextAlloc(context, sizeof(TSMapCase)); + + memset(resultCaseObject, 0, sizeof(TSMapCase)); + result->value.objectCase = resultCaseObject; + result->type = TSMAP_CASE; + + resultCaseObject->match = caseObject->match; + + resultCaseObject->command = TSMapMoveToMemoryContext(caseObject->command, context); + resultCaseObject->command->parent = result; + + resultCaseObject->condition = TSMapMoveToMemoryContext(caseObject->condition, context); + resultCaseObject->condition->parent = result; + + if (caseObject->elsebranch != NULL) + { + resultCaseObject->elsebranch = TSMapMoveToMemoryContext(caseObject->elsebranch, context); + resultCaseObject->elsebranch->parent = result; + } + + return result; +} + +/* + * Move a FTS configuration to another memory context + */ +TSMapElement * +TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context) +{ + TSMapElement *result = NULL; + + switch (config->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionMoveToMemoryContext(config->value.objectExpression, context); + break; + case TSMAP_CASE: + result = TSMapCaseMoveToMemoryContext(config->value.objectCase, context); + break; + case TSMAP_DICTIONARY: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_DICTIONARY; + result->value.objectDictionary = config->value.objectDictionary; + break; + case TSMAP_KEEP: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_KEEP; + result->value.object = NULL; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + break; + } + + return result; +} + +/* + * Free memory occupied by FTS configuration expression + */ +static void +TSMapExpressionFree(TSMapExpression *expression) +{ + if (expression->left) + TSMapElementFree(expression->left); + if (expression->right) + TSMapElementFree(expression->right); + pfree(expression); +} + +/* + * Free memory occupied by FTS configuration case + */ +static void +TSMapCaseFree(TSMapCase *caseObject) +{ + TSMapElementFree(caseObject->condition); + TSMapElementFree(caseObject->command); + TSMapElementFree(caseObject->elsebranch); + pfree(caseObject); +} + +/* + * Free memory occupied by FTS configuration element + */ +void +TSMapElementFree(TSMapElement *element) +{ + if (element != NULL) + { + switch (element->type) + { + case TSMAP_CASE: + TSMapCaseFree(element->value.objectCase); + break; + case TSMAP_EXPRESSION: + TSMapExpressionFree(element->value.objectExpression); + break; + } + pfree(element); + } +} + +/* + * Do a deep comparison of two TSMapElements. Doesn't check parents of elements + */ +bool +TSMapElementEquals(TSMapElement *a, TSMapElement *b) +{ + bool result = true; + + if (a->type == b->type) + { + switch (a->type) + { + case TSMAP_CASE: + if (!TSMapElementEquals(a->value.objectCase->condition, b->value.objectCase->condition)) + result = false; + if (!TSMapElementEquals(a->value.objectCase->command, b->value.objectCase->command)) + result = false; + + if (a->value.objectCase->elsebranch != NULL && b->value.objectCase->elsebranch != NULL) + { + if (!TSMapElementEquals(a->value.objectCase->elsebranch, b->value.objectCase->elsebranch)) + result = false; + } + else if (a->value.objectCase->elsebranch != NULL || b->value.objectCase->elsebranch != NULL) + result = false; + + if (a->value.objectCase->match != b->value.objectCase->match) + result = false; + break; + case TSMAP_EXPRESSION: + if (!TSMapElementEquals(a->value.objectExpression->left, b->value.objectExpression->left)) + result = false; + if (!TSMapElementEquals(a->value.objectExpression->right, b->value.objectExpression->right)) + result = false; + if (a->value.objectExpression->operator != b->value.objectExpression->operator) + result = false; + break; + case TSMAP_DICTIONARY: + result = a->value.objectDictionary == b->value.objectDictionary; + break; + case TSMAP_KEEP: + result = true; + } + } + else + result = false; + + return result; +} diff --git a/src/backend/tsearch/ts_parse.c b/src/backend/tsearch/ts_parse.c index 7b69ef5..f476abb 100644 --- a/src/backend/tsearch/ts_parse.c +++ b/src/backend/tsearch/ts_parse.c @@ -16,58 +16,157 @@ #include "tsearch/ts_cache.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "funcapi.h" #define IGNORE_LONGLEXEME 1 -/* +/*------------------- * Lexize subsystem + *------------------- */ +/* + * Representation of token produced by FTS parser. It contains intermediate + * lexemes in case of phrase dictionary processing. + */ typedef struct ParsedLex { - int type; - char *lemm; - int lenlemm; - struct ParsedLex *next; + int type; /* Token type */ + char *lemm; /* Token itself */ + int lenlemm; /* Length of the token string */ + int maplen; /* Length of the map */ + bool *accepted; /* Is accepted by some dictionary */ + bool *rejected; /* Is rejected by all dictionaries */ + bool *notFinished; /* Some dictionary not finished processing and + * waits for more tokens */ + struct ParsedLex *next; /* Next token in the list */ + TSMapElement *relatedRule; /* Rule which is used to produce lexemes from + * the token */ } ParsedLex; +/* + * List of tokens produced by FTS parser. + */ typedef struct ListParsedLex { ParsedLex *head; ParsedLex *tail; } ListParsedLex; -typedef struct +/* + * Dictionary state shared between processing of different tokens + */ +typedef struct DictState { - TSConfigCacheEntry *cfg; - Oid curDictId; - int posDict; - DictSubState dictState; - ParsedLex *curSub; - ListParsedLex towork; /* current list to work */ - ListParsedLex waste; /* list of lexemes that already lexized */ + Oid relatedDictionary; /* DictState contains state of dictionary + * with this Oid */ + DictSubState subState; /* Internal state of the dictionary used to + * store some state between dictionary calls */ + ListParsedLex acceptedTokens; /* Tokens which are processed and + * accepted, used in last returned result + * by the dictionary */ + ListParsedLex intermediateTokens; /* Tokens which are not accepted, but + * were processed by thesaurus-like + * dictionary */ + bool storeToAccepted; /* Should current token be appended to + * accepted or intermediate tokens */ + bool processed; /* Is the dictionary take control during + * current token processing */ + TSLexeme *tmpResult; /* Last result returned by thesaurus-like + * dictionary, if dictionary still waiting for + * more lexemes */ +} DictState; - /* - * fields to store last variant to lexize (basically, thesaurus or similar - * to, which wants several lexemes - */ +/* + * List of dictionary states + */ +typedef struct DictStateList +{ + int listLength; + DictState *states; +} DictStateList; - ParsedLex *lastRes; - TSLexeme *tmpRes; +/* + * Buffer entry with lexemes produced from current token + */ +typedef struct LexemesBufferEntry +{ + TSMapElement *key; /* Element of the mapping configuration produced the entry */ + ParsedLex *token; /* Token used for production of the lexemes */ + TSLexeme *data; /* Lexemes produced from current token */ +} LexemesBufferEntry; + +/* + * Buffer with lexemes produced from current token + */ +typedef struct LexemesBuffer +{ + int size; + LexemesBufferEntry *data; +} LexemesBuffer; + +/* + * Storage for accepted and possible accepted lexemes + */ +typedef struct ResultStorage +{ + TSLexeme *lexemes; /* Processed lexemes, which is not yet + * accepted */ + TSLexeme *accepted; /* Already accepted lexemes */ +} ResultStorage; + +/* + * FTS processing context + */ +typedef struct LexizeData +{ + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + DictStateList dslist; /* List of all currently stored states of + * dictionaries */ + ListParsedLex towork; /* Current list to work */ + ListParsedLex waste; /* List of lexemes that already lexized */ + LexemesBuffer buffer; /* Buffer of processed lexemes. Used to avoid + * multiple execution of token lexize process + * with same parameters */ + ResultStorage delayedResults; /* Results that should be returned but may + * be rejected in future */ + Oid skipDictionary; /* The dictionary we should skip during + * processing. Used to avoid infinite loop in + * configuration with phrase dictionary */ + bool debugContext; /* If true, relatedRule attribute is filled */ } LexizeData; -static void -LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +/* + * FTS processing debug context. Used during ts_debug calls. + */ +typedef struct TSDebugContext { - ld->cfg = cfg; - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->towork.head = ld->towork.tail = ld->curSub = NULL; - ld->waste.head = ld->waste.tail = NULL; - ld->lastRes = NULL; - ld->tmpRes = NULL; -} + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + TSParserCacheEntry *prsobj; /* Parser context of current ts_debug context */ + LexDescr *tokenTypes; /* Token types supported by current parser */ + void *prsdata; /* Parser data of current ts_debug context */ + LexizeData ldata; /* Lexize data of current ts_debug context */ + int tokentype; /* Last token tokentype */ + TSLexeme *savedLexemes; /* Last token lexemes stored for ts_debug + * output */ + ParsedLex *leftTokens; /* Corresponded ParsedLex */ +} TSDebugContext; + +static TSLexeme *TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression); +static TSLexeme *LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config); + +/*------------------- + * ListParsedLex API + *------------------- + */ +/* + * Add a ParsedLex to the end of the list + */ static void LPLAddTail(ListParsedLex *list, ParsedLex *newpl) { @@ -81,274 +180,1291 @@ LPLAddTail(ListParsedLex *list, ParsedLex *newpl) newpl->next = NULL; } -static ParsedLex * -LPLRemoveHead(ListParsedLex *list) -{ - ParsedLex *res = list->head; +/* + * Add a copy of ParsedLex to the end of the list + */ +static void +LPLAddTailCopy(ListParsedLex *list, ParsedLex *newpl) +{ + ParsedLex *copy = palloc0(sizeof(ParsedLex)); + + copy->lenlemm = newpl->lenlemm; + copy->type = newpl->type; + copy->lemm = newpl->lemm; + copy->relatedRule = newpl->relatedRule; + copy->next = NULL; + + if (list->tail) + { + list->tail->next = copy; + list->tail = copy; + } + else + list->head = list->tail = copy; +} + +/* + * Remove the head of the list. Return pointer to detached head + */ +static ParsedLex * +LPLRemoveHead(ListParsedLex *list) +{ + ParsedLex *res = list->head; + + if (list->head) + list->head = list->head->next; + + if (list->head == NULL) + list->tail = NULL; + + return res; +} + +/* + * Remove all ParsedLex from the list + */ +static void +LPLClear(ListParsedLex *list) +{ + ParsedLex *tmp, + *ptr = list->head; + + while (ptr) + { + tmp = ptr->next; + pfree(ptr); + ptr = tmp; + } + + list->head = list->tail = NULL; +} + +/*------------------- + * LexizeData manipulation functions + *------------------- + */ + +/* + * Initialize empty LexizeData object + */ +static void +LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +{ + ld->cfg = cfg; + ld->skipDictionary = InvalidOid; + ld->towork.head = ld->towork.tail = NULL; + ld->waste.head = ld->waste.tail = NULL; + ld->dslist.listLength = 0; + ld->dslist.states = NULL; + ld->buffer.size = 0; + ld->buffer.data = NULL; + ld->delayedResults.lexemes = NULL; + ld->delayedResults.accepted = NULL; +} + +/* + * Add a token to the processing queue + */ +static void +LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) +{ + ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + + newpl->type = type; + newpl->lemm = lemm; + newpl->lenlemm = lenlemm; + newpl->relatedRule = NULL; + LPLAddTail(&ld->towork, newpl); +} + +/* + * Remove head of the processing queue + */ +static void +RemoveHead(LexizeData *ld) +{ + LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); +} + +/* + * Set token corresponded to current lexeme + */ +static void +setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +{ + if (correspondLexem) + *correspondLexem = ld->waste.head; + else + LPLClear(&ld->waste); + + ld->waste.head = ld->waste.tail = NULL; +} + +/*------------------- + * DictState manipulation functions + *------------------- + */ + +/* + * Get a state of dictionary based on its OID + */ +static DictState * +DictStateListGet(DictStateList *list, Oid dictId) +{ + int i; + DictState *result = NULL; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + result = &list->states[i]; + + return result; +} + +/* + * Remove a state of dictionary based on its OID + */ +static void +DictStateListRemove(DictStateList *list, Oid dictId) +{ + int i; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + break; + + if (i != list->listLength) + { + memcpy(list->states + i, list->states + i + 1, sizeof(DictState) * (list->listLength - i - 1)); + list->listLength--; + if (list->listLength == 0) + list->states = NULL; + else + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + } +} + +/* + * Insert a state of dictionary with specified OID + */ +static DictState * +DictStateListAdd(DictStateList *list, DictState *state) +{ + DictStateListRemove(list, state->relatedDictionary); + + list->listLength++; + if (list->states) + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + else + list->states = palloc0(sizeof(DictState) * list->listLength); + + memcpy(list->states + list->listLength - 1, state, sizeof(DictState)); + + return list->states + list->listLength - 1; +} + +/* + * Remove states of all dictionaries + */ +static void +DictStateListClear(DictStateList *list) +{ + list->listLength = 0; + if (list->states) + pfree(list->states); + list->states = NULL; +} + +/*------------------- + * LexemesBuffer manipulation functions + *------------------- + */ + +/* + * Check if there is a saved lexeme generated by specified TSMapElement + */ +static bool +LexemesBufferContains(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + return true; + + return false; +} + +/* + * Get a saved lexeme generated by specified TSMapElement + */ +static TSLexeme * +LexemesBufferGet(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + TSLexeme *result = NULL; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + result = buffer->data[i].data; + + return result; +} + +/* + * Remove a saved lexeme generated by specified TSMapElement + */ +static void +LexemesBufferRemove(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + break; + + if (i != buffer->size) + { + memcpy(buffer->data + i, buffer->data + i + 1, sizeof(LexemesBufferEntry) * (buffer->size - i - 1)); + buffer->size--; + if (buffer->size == 0) + buffer->data = NULL; + else + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + } +} + +/* + * Same a lexeme generated by specified TSMapElement + */ +static void +LexemesBufferAdd(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token, TSLexeme *data) +{ + LexemesBufferRemove(buffer, key, token); + + buffer->size++; + if (buffer->data) + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + else + buffer->data = palloc0(sizeof(LexemesBufferEntry) * buffer->size); + + buffer->data[buffer->size - 1].token = token; + buffer->data[buffer->size - 1].key = key; + buffer->data[buffer->size - 1].data = data; +} + +/* + * Remove all lexemes saved in a buffer + */ +static void +LexemesBufferClear(LexemesBuffer *buffer) +{ + int i; + bool *skipEntry = palloc0(sizeof(bool) * buffer->size); + + for (i = 0; i < buffer->size; i++) + { + if (buffer->data[i].data != NULL && !skipEntry[i]) + { + int j; + + for (j = 0; j < buffer->size; j++) + if (buffer->data[i].data == buffer->data[j].data) + skipEntry[j] = true; + + pfree(buffer->data[i].data); + } + } + + buffer->size = 0; + if (buffer->data) + pfree(buffer->data); + buffer->data = NULL; +} + +/*------------------- + * TSLexeme util functions + *------------------- + */ + +/* + * Get size of TSLexeme except empty-lexeme + */ +static int +TSLexemeGetSize(TSLexeme *lex) +{ + int result = 0; + TSLexeme *ptr = lex; + + while (ptr && ptr->lexeme) + { + result++; + ptr++; + } + + return result; +} + +/* + * Remove repeated lexemes. Also remove copies of whole nvariant groups. + */ +static TSLexeme * +TSLexemeRemoveDuplications(TSLexeme *lexeme) +{ + TSLexeme *res; + int curLexIndex; + int i; + int lexemeSize = TSLexemeGetSize(lexeme); + int shouldCopyCount = lexemeSize; + bool *shouldCopy; + + if (lexeme == NULL) + return NULL; + + shouldCopy = palloc(sizeof(bool) * lexemeSize); + memset(shouldCopy, true, sizeof(bool) * lexemeSize); + + for (curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + for (i = curLexIndex + 1; i < lexemeSize; i++) + { + if (!shouldCopy[i]) + continue; + + if (strcmp(lexeme[curLexIndex].lexeme, lexeme[i].lexeme) == 0) + { + if (lexeme[curLexIndex].nvariant == lexeme[i].nvariant) + { + shouldCopy[i] = false; + shouldCopyCount--; + continue; + } + else + { + /* + * Check for same set of lexemes in another nvariant + * series + */ + int nvariantCountL = 0; + int nvariantCountR = 0; + int nvariantOverlap = 1; + int j; + + for (j = 0; j < lexemeSize; j++) + if (lexeme[curLexIndex].nvariant == lexeme[j].nvariant) + nvariantCountL++; + for (j = 0; j < lexemeSize; j++) + if (lexeme[i].nvariant == lexeme[j].nvariant) + nvariantCountR++; + + if (nvariantCountL != nvariantCountR) + continue; + + for (j = 1; j < nvariantCountR; j++) + { + if (strcmp(lexeme[curLexIndex + j].lexeme, lexeme[i + j].lexeme) == 0 + && lexeme[curLexIndex + j].nvariant == lexeme[i + j].nvariant) + nvariantOverlap++; + } + + if (nvariantOverlap != nvariantCountR) + continue; + + for (j = 0; j < nvariantCountR; j++) + shouldCopy[i + j] = false; + } + } + } + } + + res = palloc0(sizeof(TSLexeme) * (shouldCopyCount + 1)); + + for (i = 0, curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + if (shouldCopy[curLexIndex]) + { + memcpy(res + i, lexeme + curLexIndex, sizeof(TSLexeme)); + i++; + } + } + + pfree(shouldCopy); + return res; +} + +/* + * Combine two lexeme lists with respect to positions + */ +static TSLexeme * +TSLexemeMergePositions(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + + if (left != NULL || right != NULL) + { + int left_i = 0; + int right_i = 0; + int left_max_nvariant = 0; + int i; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + for (i = 0; i < right_size; i++) + right[i].nvariant += left_max_nvariant; + if (right && right[0].flags & TSL_ADDPOS) + right[0].flags &= ~TSL_ADDPOS; + + i = 0; + while (i < left_size + right_size) + { + if (left_i < left_size) + { + do + { + result[i++] = left[left_i++]; + } while (left && left[left_i].lexeme && (left[left_i].flags & TSL_ADDPOS) == 0); + } + + if (right_i < right_size) + { + do + { + result[i++] = right[right_i++]; + } while (right && right[right_i].lexeme && (right[right_i].flags & TSL_ADDPOS) == 0); + } + } + } + return result; +} + +/* + * Split lexemes generated by regular dictionaries and multi-input dictionaries + * and combine them with respect to positions + */ +static TSLexeme * +TSLexemeFilterMulti(TSLexeme *lexemes) +{ + TSLexeme *result; + TSLexeme *ptr = lexemes; + int multi_lexemes = 0; + + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + multi_lexemes++; + ptr++; + } + + if (multi_lexemes > 0) + { + TSLexeme *lexemes_multi = palloc0(sizeof(TSLexeme) * (multi_lexemes + 1)); + TSLexeme *lexemes_rest = palloc0(sizeof(TSLexeme) * (TSLexemeGetSize(lexemes) - multi_lexemes + 1)); + int rest_i = 0; + int multi_i = 0; + + ptr = lexemes; + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + lexemes_multi[multi_i++] = *ptr; + else + lexemes_rest[rest_i++] = *ptr; + + ptr++; + } + result = TSLexemeMergePositions(lexemes_rest, lexemes_multi); + } + else + { + result = TSLexemeMergePositions(lexemes, NULL); + } + + return result; +} + +/* + * Mark lexemes as generated by multi-input (thesaurus-like) dictionary + */ +static void +TSLexemeMarkMulti(TSLexeme *lexemes) +{ + TSLexeme *ptr = lexemes; + + while (ptr && ptr->lexeme) + { + ptr->flags |= TSL_MULTI; + ptr++; + } +} + +/*------------------- + * Lexemes set operations + *------------------- + */ + +/* + * Combine left and right lexeme lists into one. + * If append is true, right lexemes added after last left lexeme with TSL_ADDPOS flag + */ +static TSLexeme * +TSLexemeUnionOpt(TSLexeme *left, TSLexeme *right, bool append) +{ + TSLexeme *result; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + int left_max_nvariant = 0; + int i; + + if (left == NULL && right == NULL) + { + result = NULL; + } + else + { + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + if (left_size > 0) + memcpy(result, left, sizeof(TSLexeme) * left_size); + if (right_size > 0) + memcpy(result + left_size, right, sizeof(TSLexeme) * right_size); + if (append && left_size > 0 && right_size > 0) + result[left_size].flags |= TSL_ADDPOS; + + for (i = left_size; i < left_size + right_size; i++) + result[i].nvariant += left_max_nvariant; + } + + return result; +} + +/* + * Combine left and right lexeme lists into one + */ +static TSLexeme * +TSLexemeUnion(TSLexeme *left, TSLexeme *right) +{ + return TSLexemeUnionOpt(left, right, false); +} + +/* + * Remove common lexemes and return only which is stored in left list + */ +static TSLexeme * +TSLexemeExcept(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (!found) + result[k++] = left[i]; + } + + return result; +} + +/* + * Keep only common lexemes + */ +static TSLexeme * +TSLexemeIntersect(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (found) + result[k++] = left[i]; + } + + return result; +} + +/*------------------- + * Result storage functions + *------------------- + */ + +/* + * Add a lexeme to the result storage + */ +static void +ResultStorageAdd(ResultStorage *storage, ParsedLex *token, TSLexeme *lexs) +{ + TSLexeme *oldLexs = storage->lexemes; + + storage->lexemes = TSLexemeUnionOpt(storage->lexemes, lexs, true); + if (oldLexs) + pfree(oldLexs); +} + +/* + * Move all saved lexemes to accepted list + */ +static void +ResultStorageMoveToAccepted(ResultStorage *storage) +{ + if (storage->accepted) + { + TSLexeme *prevAccepted = storage->accepted; + + storage->accepted = TSLexemeUnionOpt(storage->accepted, storage->lexemes, true); + if (prevAccepted) + pfree(prevAccepted); + if (storage->lexemes) + pfree(storage->lexemes); + } + else + { + storage->accepted = storage->lexemes; + } + storage->lexemes = NULL; +} + +/* + * Remove all non-accepted lexemes + */ +static void +ResultStorageClearLexemes(ResultStorage *storage) +{ + if (storage->lexemes) + pfree(storage->lexemes); + storage->lexemes = NULL; +} + +/* + * Remove all accepted lexemes + */ +static void +ResultStorageClearAccepted(ResultStorage *storage) +{ + if (storage->accepted) + pfree(storage->accepted); + storage->accepted = NULL; +} + +/*------------------- + * Condition and command execution + *------------------- + */ + +/* + * Process a token by the dictionary + */ +static TSLexeme * +LexizeExecDictionary(LexizeData *ld, ParsedLex *token, TSMapElement *dictionary) +{ + TSLexeme *res; + TSDictionaryCacheEntry *dict; + DictSubState subState; + Oid dictId = dictionary->value.objectDictionary; + + if (ld->skipDictionary == dictId) + return NULL; + + if (LexemesBufferContains(&ld->buffer, dictionary, token)) + res = LexemesBufferGet(&ld->buffer, dictionary, token); + else + { + char *curValLemm = token->lemm; + int curValLenLemm = token->lenlemm; + DictState *state = DictStateListGet(&ld->dslist, dictId); + + dict = lookup_ts_dictionary_cache(dictId); + + if (state) + { + subState = state->subState; + state->processed = true; + } + else + { + subState.isend = subState.getnext = false; + subState.private_state = NULL; + } + + res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(dict->lexize), + PointerGetDatum(dict->dictData), + PointerGetDatum(curValLemm), + Int32GetDatum(curValLenLemm), + PointerGetDatum(&subState) + )); + + if (subState.getnext) + { + /* + * Dictionary wants next word, so store current context and state + * in the DictStateList + */ + if (state == NULL) + { + state = palloc0(sizeof(DictState)); + state->processed = true; + state->relatedDictionary = dictId; + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + state->acceptedTokens.head = state->acceptedTokens.tail = NULL; + state->tmpResult = NULL; + + /* + * Add state to the list and update pointer in order to work + * with copy from the list + */ + state = DictStateListAdd(&ld->dslist, state); + } + + state->subState = subState; + state->storeToAccepted = res != NULL; + + if (res) + { + if (state->intermediateTokens.head != NULL) + { + ParsedLex *ptr = state->intermediateTokens.head; + + while (ptr) + { + LPLAddTailCopy(&state->acceptedTokens, ptr); + ptr = ptr->next; + } + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + } + + if (state->tmpResult) + pfree(state->tmpResult); + TSLexemeMarkMulti(res); + state->tmpResult = res; + res = NULL; + } + } + else if (state != NULL) + { + if (res) + { + if (state) + TSLexemeMarkMulti(res); + DictStateListRemove(&ld->dslist, dictId); + } + else + { + /* + * Trigger post-processing in order to check tmpResult and + * restart processing (see LexizeExec function) + */ + state->processed = false; + } + } + LexemesBufferAdd(&ld->buffer, dictionary, token, res); + } + + return res; +} + +/* + * Check is dictionary waits for more tokens or not + */ +static bool +LexizeExecDictionaryWaitNext(LexizeData *ld, Oid dictId) +{ + DictState *state = DictStateListGet(&ld->dslist, dictId); + + if (state) + return state->subState.getnext; + else + return false; +} + +/* + * Check is dictionary result for current token is NULL or not. + * It dictionary waits for more lexemes, the result is interpreted as not null. + */ +static bool +LexizeExecIsNull(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + bool result = false; + + if (config->type == TSMAP_EXPRESSION) + { + TSMapExpression *expression = config->value.objectExpression; + + result = LexizeExecIsNull(ld, token, expression->left) || LexizeExecIsNull(ld, token, expression->right); + } + else if (config->type == TSMAP_DICTIONARY) + { + Oid dictOid = config->value.objectDictionary; + TSLexeme *lexemes = LexizeExecDictionary(ld, token, config); + + if (lexemes) + result = false; + else + result = !LexizeExecDictionaryWaitNext(ld, dictOid); + } + return result; +} + +/* + * Execute a MAP operator + */ +static TSLexeme * +TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression) +{ + TSLexeme *left_res; + TSLexeme *result = NULL; + int left_size; + int i; + + left_res = LexizeExecTSElement(ld, token, expression->left); + left_size = TSLexemeGetSize(left_res); + + if (left_res == NULL && LexizeExecIsNull(ld, token, expression->left)) + result = LexizeExecTSElement(ld, token, expression->right); + else if (expression->operator == TSMAP_OP_COMMA && + ((left_res != NULL && (left_res->flags & TSL_FILTER) == 0) || left_res == NULL)) + result = left_res; + else + { + TSMapElement *relatedRuleTmp = NULL; + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + relatedRuleTmp->value.objectExpression->left = token->relatedRule; + + for (i = 0; i < left_size; i++) + { + TSLexeme *tmp_res = NULL; + TSLexeme *prev_res; + ParsedLex tmp_token; + + tmp_token.lemm = left_res[i].lexeme; + tmp_token.lenlemm = strlen(left_res[i].lexeme); + tmp_token.type = token->type; + tmp_token.next = NULL; + + tmp_res = LexizeExecTSElement(ld, &tmp_token, expression->right); + relatedRuleTmp->value.objectExpression->right = tmp_token.relatedRule; + prev_res = result; + result = TSLexemeUnion(prev_res, tmp_res); + if (prev_res) + pfree(prev_res); + } + token->relatedRule = relatedRuleTmp; + } + + return result; +} + +/* + * Execute a TSMapElement + * Common point of all possible types of TSMapElement + */ +static TSLexeme * +LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + TSLexeme *result = NULL; + + if (LexemesBufferContains(&ld->buffer, config, token)) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexemesBufferGet(&ld->buffer, config, token); + } + else if (config->type == TSMAP_DICTIONARY) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexizeExecDictionary(ld, token, config); + } + else if (config->type == TSMAP_CASE) + { + TSMapCase *caseObject = config->value.objectCase; + bool conditionIsNull = LexizeExecIsNull(ld, token, caseObject->condition); + + if ((!conditionIsNull && caseObject->match) || (conditionIsNull && !caseObject->match)) + { + if (caseObject->command->type == TSMAP_KEEP) + result = LexizeExecTSElement(ld, token, caseObject->condition); + else + result = LexizeExecTSElement(ld, token, caseObject->command); + } + else if (caseObject->elsebranch) + result = LexizeExecTSElement(ld, token, caseObject->elsebranch); + } + else if (config->type == TSMAP_EXPRESSION) + { + TSLexeme *resLeft = NULL; + TSLexeme *resRight = NULL; + TSMapElement *relatedRuleTmp = NULL; + TSMapExpression *expression = config->value.objectExpression; + + if (expression->operator != TSMAP_OP_MAP && expression->operator != TSMAP_OP_COMMA) + { + if (ld->debugContext) + { + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + } - if (list->head) - list->head = list->head->next; + resLeft = LexizeExecTSElement(ld, token, expression->left); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->left = token->relatedRule; - if (list->head == NULL) - list->tail = NULL; + resRight = LexizeExecTSElement(ld, token, expression->right); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->right = token->relatedRule; + } - return res; -} + switch (expression->operator) + { + case TSMAP_OP_UNION: + result = TSLexemeUnion(resLeft, resRight); + break; + case TSMAP_OP_EXCEPT: + result = TSLexemeExcept(resLeft, resRight); + break; + case TSMAP_OP_INTERSECT: + result = TSLexemeIntersect(resLeft, resRight); + break; + case TSMAP_OP_MAP: + case TSMAP_OP_COMMA: + result = TSLexemeMap(ld, token, expression); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } -static void -LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) -{ - ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + if (ld->debugContext && relatedRuleTmp != NULL) + token->relatedRule = relatedRuleTmp; + } - newpl->type = type; - newpl->lemm = lemm; - newpl->lenlemm = lenlemm; - LPLAddTail(&ld->towork, newpl); - ld->curSub = ld->towork.tail; + if (!LexemesBufferContains(&ld->buffer, config, token)) + LexemesBufferAdd(&ld->buffer, config, token, result); + + return result; } -static void -RemoveHead(LexizeData *ld) +/*------------------- + * LexizeExec and helpers functions + *------------------- + */ + +/* + * Processing of EOF-like token. + * Return all temporary results if any are saved. + */ +static TSLexeme * +LexizeExecFinishProcessing(LexizeData *ld) { - LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); + int i; + TSLexeme *res = NULL; + + for (i = 0; i < ld->dslist.listLength; i++) + { + TSLexeme *last_res = res; - ld->posDict = 0; + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); + } + + return res; } -static void -setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +/* + * Get last accepted result of the phrase-dictionary + */ +static TSLexeme * +LexizeExecGetPreviousResults(LexizeData *ld) { - if (correspondLexem) - { - *correspondLexem = ld->waste.head; - } - else - { - ParsedLex *tmp, - *ptr = ld->waste.head; + int i; + TSLexeme *res = NULL; - while (ptr) + for (i = 0; i < ld->dslist.listLength; i++) + { + if (!ld->dslist.states[i].processed) { - tmp = ptr->next; - pfree(ptr); - ptr = tmp; + TSLexeme *last_res = res; + + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); } } - ld->waste.head = ld->waste.tail = NULL; + + return res; } +/* + * Remove all dictionary states which wasn't used for current token + */ static void -moveToWaste(LexizeData *ld, ParsedLex *stop) +LexizeExecClearDictStates(LexizeData *ld) { - bool go = true; + int i; - while (ld->towork.head && go) + for (i = 0; i < ld->dslist.listLength; i++) { - if (ld->towork.head == stop) + if (!ld->dslist.states[i].processed) { - ld->curSub = stop->next; - go = false; + DictStateListRemove(&ld->dslist, ld->dslist.states[i].relatedDictionary); + i = 0; } - RemoveHead(ld); } } -static void -setNewTmpRes(LexizeData *ld, ParsedLex *lex, TSLexeme *res) +/* + * Check if there are any dictionaries that didn't processed current token + */ +static bool +LexizeExecNotProcessedDictStates(LexizeData *ld) { - if (ld->tmpRes) - { - TSLexeme *ptr; + int i; - for (ptr = ld->tmpRes; ptr->lexeme; ptr++) - pfree(ptr->lexeme); - pfree(ld->tmpRes); - } - ld->tmpRes = res; - ld->lastRes = lex; + for (i = 0; i < ld->dslist.listLength; i++) + if (!ld->dslist.states[i].processed) + return true; + + return false; } +/* + * Do a lexize processing for a towork queue in LexizeData + */ static TSLexeme * LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) { + ParsedLex *token; + TSMapElement *config; + TSLexeme *res = NULL; + TSLexeme *prevIterationResult = NULL; + bool removeHead = false; + bool resetSkipDictionary = false; + bool accepted = false; int i; - ListDictionary *map; - TSDictionaryCacheEntry *dict; - TSLexeme *res; - if (ld->curDictId == InvalidOid) + for (i = 0; i < ld->dslist.listLength; i++) + ld->dslist.states[i].processed = false; + if (ld->skipDictionary != InvalidOid) + resetSkipDictionary = true; + + token = ld->towork.head; + if (token == NULL) { - /* - * usual mode: dictionary wants only one word, but we should keep in - * mind that we should go through all stack - */ + setCorrLex(ld, correspondLexem); + return NULL; + } - while (ld->towork.head) + if (token->type >= ld->cfg->lenmap) + { + removeHead = true; + } + else + { + config = ld->cfg->map[token->type]; + if (config != NULL) + { + res = LexizeExecTSElement(ld, token, config); + prevIterationResult = LexizeExecGetPreviousResults(ld); + removeHead = prevIterationResult == NULL; + } + else { - ParsedLex *curVal = ld->towork.head; - char *curValLemm = curVal->lemm; - int curValLenLemm = curVal->lenlemm; + removeHead = true; + if (token->type == 0) /* Processing EOF-like token */ + { + res = LexizeExecFinishProcessing(ld); + prevIterationResult = NULL; + } + } - map = ld->cfg->map + curVal->type; + if (LexizeExecNotProcessedDictStates(ld) && (token->type == 0 || config != NULL)) /* Rollback processing */ + { + int i; + ListParsedLex *intermediateTokens = NULL; + ListParsedLex *acceptedTokens = NULL; - if (curVal->type == 0 || curVal->type >= ld->cfg->lenmap || map->len == 0) + for (i = 0; i < ld->dslist.listLength; i++) { - /* skip this type of lexeme */ - RemoveHead(ld); - continue; + if (!ld->dslist.states[i].processed) + { + intermediateTokens = &ld->dslist.states[i].intermediateTokens; + acceptedTokens = &ld->dslist.states[i].acceptedTokens; + if (prevIterationResult == NULL) + ld->skipDictionary = ld->dslist.states[i].relatedDictionary; + } } - for (i = ld->posDict; i < map->len; i++) + if (intermediateTokens && intermediateTokens->head) { - dict = lookup_ts_dictionary_cache(map->dictIds[i]); - - ld->dictState.isend = ld->dictState.getnext = false; - ld->dictState.private_state = NULL; - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curValLemm), - Int32GetDatum(curValLenLemm), - PointerGetDatum(&ld->dictState) - )); - - if (ld->dictState.getnext) + ParsedLex *head = ld->towork.head; + + ld->towork.head = intermediateTokens->head; + intermediateTokens->tail->next = head; + head->next = NULL; + ld->towork.tail = head; + removeHead = false; + LPLClear(&ld->waste); + if (acceptedTokens && acceptedTokens->head) { - /* - * dictionary wants next word, so setup and store current - * position and go to multiword mode - */ - - ld->curDictId = DatumGetObjectId(map->dictIds[i]); - ld->posDict = i + 1; - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - return LexizeExec(ld, correspondLexem); + ld->waste.head = acceptedTokens->head; + ld->waste.tail = acceptedTokens->tail; } + } + ResultStorageClearLexemes(&ld->delayedResults); + if (config != NULL) + res = NULL; + } - if (!res) /* dictionary doesn't know this lexeme */ - continue; + if (config != NULL) + LexizeExecClearDictStates(ld); + else if (token->type == 0) + DictStateListClear(&ld->dslist); + } - if (res->flags & TSL_FILTER) - { - curValLemm = res->lexeme; - curValLenLemm = strlen(res->lexeme); - continue; - } + if (prevIterationResult) + res = prevIterationResult; + else + { + int i; - RemoveHead(ld); - setCorrLex(ld, correspondLexem); - return res; + for (i = 0; i < ld->dslist.listLength; i++) + { + if (ld->dslist.states[i].storeToAccepted) + { + LPLAddTailCopy(&ld->dslist.states[i].acceptedTokens, token); + accepted = true; + ld->dslist.states[i].storeToAccepted = false; + } + else + { + LPLAddTailCopy(&ld->dslist.states[i].intermediateTokens, token); } - - RemoveHead(ld); } } - else - { /* curDictId is valid */ - dict = lookup_ts_dictionary_cache(ld->curDictId); + if (removeHead) + RemoveHead(ld); + + if (ld->dslist.listLength > 0) + { /* - * Dictionary ld->curDictId asks us about following words + * There is at least one thesaurus dictionary in the middle of + * processing. Delay return of the result to avoid wrong lexemes in + * case of thesaurus phrase rejection. */ + ResultStorageAdd(&ld->delayedResults, token, res); + if (accepted) + ResultStorageMoveToAccepted(&ld->delayedResults); - while (ld->curSub) + /* + * Current value of res should not be cleared, because it is stored in + * LexemesBuffer + */ + res = NULL; + } + else + { + if (ld->towork.head == NULL) { - ParsedLex *curVal = ld->curSub; - - map = ld->cfg->map + curVal->type; - - if (curVal->type != 0) - { - bool dictExists = false; - - if (curVal->type >= ld->cfg->lenmap || map->len == 0) - { - /* skip this type of lexeme */ - ld->curSub = curVal->next; - continue; - } + TSLexeme *oldAccepted = ld->delayedResults.accepted; - /* - * We should be sure that current type of lexeme is recognized - * by our dictionary: we just check is it exist in list of - * dictionaries ? - */ - for (i = 0; i < map->len && !dictExists; i++) - if (ld->curDictId == DatumGetObjectId(map->dictIds[i])) - dictExists = true; - - if (!dictExists) - { - /* - * Dictionary can't work with current tpe of lexeme, - * return to basic mode and redo all stored lexemes - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } - } + ld->delayedResults.accepted = TSLexemeUnionOpt(ld->delayedResults.accepted, ld->delayedResults.lexemes, true); + if (oldAccepted) + pfree(oldAccepted); + } - ld->dictState.isend = (curVal->type == 0) ? true : false; - ld->dictState.getnext = false; + /* + * Add accepted delayed results to the output of the parsing. All + * lexemes returned during thesaurus phrase processing should be + * returned simultaneously, since all phrase tokens are processed as + * one. + */ + if (ld->delayedResults.accepted != NULL) + { + /* + * Previous value of res should not be cleared, because it is + * stored in LexemesBuffer + */ + res = TSLexemeUnionOpt(ld->delayedResults.accepted, res, prevIterationResult == NULL); - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curVal->lemm), - Int32GetDatum(curVal->lenlemm), - PointerGetDatum(&ld->dictState) - )); + ResultStorageClearLexemes(&ld->delayedResults); + ResultStorageClearAccepted(&ld->delayedResults); + } + setCorrLex(ld, correspondLexem); + } - if (ld->dictState.getnext) - { - /* Dictionary wants one more */ - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - continue; - } + if (resetSkipDictionary) + ld->skipDictionary = InvalidOid; - if (res || ld->tmpRes) - { - /* - * Dictionary normalizes lexemes, so we remove from stack all - * used lexemes, return to basic mode and redo end of stack - * (if it exists) - */ - if (res) - { - moveToWaste(ld, ld->curSub); - } - else - { - res = ld->tmpRes; - moveToWaste(ld, ld->lastRes); - } + res = TSLexemeFilterMulti(res); + if (res) + res = TSLexemeRemoveDuplications(res); - /* reset to initial state */ - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->lastRes = NULL; - ld->tmpRes = NULL; - setCorrLex(ld, correspondLexem); - return res; - } + /* + * Copy result since it may be stored in LexemesBuffere and removed at the + * next step. + */ + if (res) + { + TSLexeme *oldRes = res; + int resSize = TSLexemeGetSize(res); - /* - * Dict don't want next lexem and didn't recognize anything, redo - * from ld->towork.head - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } + res = palloc0(sizeof(TSLexeme) * (resSize + 1)); + memcpy(res, oldRes, sizeof(TSLexeme) * resSize); } - setCorrLex(ld, correspondLexem); - return NULL; + LexemesBufferClear(&ld->buffer); + return res; } +/*------------------- + * ts_parse API functions + *------------------- + */ + /* * Parse string and lexize words. * @@ -357,7 +1473,7 @@ LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) void parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; @@ -375,36 +1491,42 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } while ((norms = LexizeExec(&ldata, NULL)) != NULL) { - TSLexeme *ptr = norms; + TSLexeme *ptr; + + ptr = norms; prs->pos++; /* set pos */ @@ -429,14 +1551,246 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) } pfree(norms); } - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } +/*------------------- + * ts_debug and helper functions + *------------------- + */ + +/* + * Free memory occupied by temporary TSMapElement + */ + +static void +ts_debug_free_rule(TSMapElement *element) +{ + if (element != NULL && element->type == TSMAP_EXPRESSION) + { + ts_debug_free_rule(element->value.objectExpression->left); + ts_debug_free_rule(element->value.objectExpression->right); + pfree(element->value.objectExpression); + pfree(element); + } +} + +/* + * Initialize SRF context and text parser for ts_debug execution. + */ +static void +ts_debug_init(Oid cfgId, text *inputText, FunctionCallInfo fcinfo) +{ + TupleDesc tupdesc; + char *buf; + int buflen; + FuncCallContext *funcctx; + MemoryContext oldcontext; + TSDebugContext *context; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + buf = text_to_cstring(inputText); + buflen = strlen(buf); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + funcctx->user_fctx = palloc0(sizeof(TSDebugContext)); + funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + context = funcctx->user_fctx; + context->cfg = lookup_ts_config_cache(cfgId); + context->prsobj = lookup_ts_parser_cache(context->cfg->prsId); + + context->tokenTypes = (LexDescr *) DatumGetPointer(OidFunctionCall1(context->prsobj->lextypeOid, + (Datum) 0)); + + context->prsdata = (void *) DatumGetPointer(FunctionCall2(&context->prsobj->prsstart, + PointerGetDatum(buf), + Int32GetDatum(buflen))); + LexizeInit(&context->ldata, context->cfg); + context->ldata.debugContext = true; + context->tokentype = 1; + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Get one token from input text and add it to processing queue. + */ +static void +ts_debug_get_token(FuncCallContext *funcctx) +{ + TSDebugContext *context; + MemoryContext oldcontext; + int lenlemm; + char *lemm = NULL; + + context = funcctx->user_fctx; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + context->tokentype = DatumGetInt32(FunctionCall3(&(context->prsobj->prstoken), + PointerGetDatum(context->prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (context->tokentype > 0 && lenlemm >= MAXSTRLEN) + { +#ifdef IGNORE_LONGLEXEME + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#else + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#endif + } + + LexizeAddLemm(&context->ldata, context->tokentype, lemm, lenlemm); + MemoryContextSwitchTo(oldcontext); +} + /* + * Parse text and print debug information, such as token type, dictionary map + * configuration, selected command and lexemes for each token. + * Arguments: regconfiguration(Oid) cfgId, text *inputText + */ +Datum +ts_debug(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TSDebugContext *context; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) + { + Oid cfgId = PG_GETARG_OID(0); + text *inputText = PG_GETARG_TEXT_P(1); + + ts_debug_init(cfgId, inputText, fcinfo); + } + + funcctx = SRF_PERCALL_SETUP(); + context = funcctx->user_fctx; + + while (context->tokentype > 0 && context->leftTokens == NULL) + { + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + ts_debug_get_token(funcctx); + + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + MemoryContextSwitchTo(oldcontext); + } + + while (context->leftTokens == NULL && context->ldata.towork.head != NULL) + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + if (context->leftTokens && context->leftTokens && context->leftTokens->type > 0) + { + HeapTuple tuple; + Datum result; + char **values; + ParsedLex *lex = context->leftTokens; + StringInfo str = NULL; + TSLexeme *ptr; + + values = palloc0(sizeof(char *) * 7); + str = makeStringInfo(); + initStringInfo(str); + + values[0] = context->tokenTypes[lex->type - 1].alias; + values[1] = context->tokenTypes[lex->type - 1].descr; + + values[2] = palloc0(sizeof(char) * (lex->lenlemm + 1)); + memcpy(values[2], lex->lemm, sizeof(char) * lex->lenlemm); + + initStringInfo(str); + appendStringInfoChar(str, '{'); + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + Oid *dictionaries = TSMapGetDictionaries(context->ldata.cfg->map[lex->type]); + Oid *currentDictionary = NULL; + for (currentDictionary = dictionaries; *currentDictionary != InvalidOid; currentDictionary++) + { + if (currentDictionary != dictionaries) + appendStringInfoChar(str, ','); + + TSMapPrintDictName(*currentDictionary, str); + } + } + appendStringInfoChar(str, '}'); + values[3] = str->data; + + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + initStringInfo(str); + TSMapPrintElement(context->ldata.cfg->map[lex->type], str); + values[4] = str->data; + + initStringInfo(str); + if (lex->relatedRule) + { + TSMapPrintElement(lex->relatedRule, str); + values[5] = str->data; + str = makeStringInfo(); + initStringInfo(str); + ts_debug_free_rule(lex->relatedRule); + lex->relatedRule = NULL; + } + } + + initStringInfo(str); + ptr = context->savedLexemes; + if (context->savedLexemes) + appendStringInfoChar(str, '{'); + + while (ptr && ptr->lexeme) + { + if (ptr != context->savedLexemes) + appendStringInfoString(str, ", "); + appendStringInfoString(str, ptr->lexeme); + ptr++; + } + if (context->savedLexemes) + appendStringInfoChar(str, '}'); + if (context->savedLexemes) + values[6] = str->data; + else + values[6] = NULL; + + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + result = HeapTupleGetDatum(tuple); + + context->leftTokens = lex->next; + pfree(lex); + if (context->leftTokens == NULL && context->savedLexemes) + pfree(context->savedLexemes); + + SRF_RETURN_NEXT(funcctx, result); + } + + FunctionCall1(&(context->prsobj->prsend), PointerGetDatum(context->prsdata)); + SRF_RETURN_DONE(funcctx); +} + +/*------------------- * Headline framework + *------------------- */ + static void hladdword(HeadlineParsedText *prs, char *buf, int buflen, int type) { @@ -532,12 +1886,12 @@ addHLParsedLex(HeadlineParsedText *prs, TSQuery query, ParsedLex *lexs, TSLexeme void hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; TSLexeme *norms; - ParsedLex *lexs; + ParsedLex *lexs = NULL; TSConfigCacheEntry *cfg; TSParserCacheEntry *prsobj; void *prsdata; @@ -551,32 +1905,36 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } do { @@ -587,9 +1945,10 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu } else addHLParsedLex(prs, query, lexs, NULL); + lexs = NULL; } while (norms); - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } @@ -642,14 +2001,14 @@ generateHeadline(HeadlineParsedText *prs) } else if (!wrd->skip) { - if (wrd->selected) + if (wrd->selected && (wrd == prs->words || !(wrd - 1)->selected)) { memcpy(ptr, prs->startsel, prs->startsellen); ptr += prs->startsellen; } memcpy(ptr, wrd->word, wrd->len); ptr += wrd->len; - if (wrd->selected) + if (wrd->selected && ((wrd + 1 - prs->words) == prs->curwords || !(wrd + 1)->selected)) { memcpy(ptr, prs->stopsel, prs->stopsellen); ptr += prs->stopsellen; diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c index f6e03ae..0dd846b 100644 --- a/src/backend/tsearch/ts_utils.c +++ b/src/backend/tsearch/ts_utils.c @@ -20,7 +20,6 @@ #include "tsearch/ts_locale.h" #include "tsearch/ts_utils.h" - /* * Given the base name and extension of a tsearch config file, return * its full path name. The base name is assumed to be user-supplied, diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 2b38178..f251e83 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -828,11 +828,10 @@ static const struct cachedesc cacheinfo[] = { }, {TSConfigMapRelationId, /* TSCONFIGMAP */ TSConfigMapIndexId, - 3, + 2, { Anum_pg_ts_config_map_mapcfg, Anum_pg_ts_config_map_maptokentype, - Anum_pg_ts_config_map_mapseqno, 0 }, 2 diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index 9734778..1ff1a92 100644 --- a/src/backend/utils/cache/ts_cache.c +++ b/src/backend/utils/cache/ts_cache.c @@ -39,6 +39,7 @@ #include "catalog/pg_ts_template.h" #include "commands/defrem.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" @@ -51,13 +52,12 @@ /* - * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size + * MAXTOKENTYPE is arbitrary limits on the workspace size * used in lookup_ts_config_cache(). We could avoid hardwiring a limit * by making the workspace dynamically enlargeable, but it seems unlikely * to be worth the trouble. */ -#define MAXTOKENTYPE 256 -#define MAXDICTSPERTT 100 +#define MAXTOKENTYPE 256 static HTAB *TSParserCacheHash = NULL; @@ -418,11 +418,10 @@ lookup_ts_config_cache(Oid cfgId) ScanKeyData mapskey; SysScanDesc mapscan; HeapTuple maptup; - ListDictionary maplists[MAXTOKENTYPE + 1]; - Oid mapdicts[MAXDICTSPERTT]; + TSMapElement *mapconfigs[MAXTOKENTYPE + 1]; int maxtokentype; - int ndicts; int i; + TSMapElement *tmpConfig; tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); if (!HeapTupleIsValid(tp)) @@ -453,8 +452,8 @@ lookup_ts_config_cache(Oid cfgId) if (entry->map) { for (i = 0; i < entry->lenmap; i++) - if (entry->map[i].dictIds) - pfree(entry->map[i].dictIds); + if (entry->map[i]) + TSMapElementFree(entry->map[i]); pfree(entry->map); } } @@ -468,13 +467,11 @@ lookup_ts_config_cache(Oid cfgId) /* * Scan pg_ts_config_map to gather dictionary list for each token type * - * Because the index is on (mapcfg, maptokentype, mapseqno), we will - * see the entries in maptokentype order, and in mapseqno order for - * each token type, even though we didn't explicitly ask for that. + * Because the index is on (mapcfg, maptokentype), we will see the + * entries in maptokentype order even though we didn't explicitly ask + * for that. */ - MemSet(maplists, 0, sizeof(maplists)); maxtokentype = 0; - ndicts = 0; ScanKeyInit(&mapskey, Anum_pg_ts_config_map_mapcfg, @@ -486,6 +483,7 @@ lookup_ts_config_cache(Oid cfgId) mapscan = systable_beginscan_ordered(maprel, mapidx, NULL, 1, &mapskey); + memset(mapconfigs, 0, sizeof(mapconfigs)); while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); @@ -495,51 +493,27 @@ lookup_ts_config_cache(Oid cfgId) elog(ERROR, "maptokentype value %d is out of range", toktype); if (toktype < maxtokentype) elog(ERROR, "maptokentype entries are out of order"); - if (toktype > maxtokentype) - { - /* starting a new token type, but first save the prior data */ - if (ndicts > 0) - { - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - } - maxtokentype = toktype; - mapdicts[0] = cfgmap->mapdict; - ndicts = 1; - } - else - { - /* continuing data for current token type */ - if (ndicts >= MAXDICTSPERTT) - elog(ERROR, "too many pg_ts_config_map entries for one token type"); - mapdicts[ndicts++] = cfgmap->mapdict; - } + + maxtokentype = toktype; + tmpConfig = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + mapconfigs[maxtokentype] = TSMapMoveToMemoryContext(tmpConfig, CacheMemoryContext); + TSMapElementFree(tmpConfig); + tmpConfig = NULL; } systable_endscan_ordered(mapscan); index_close(mapidx, AccessShareLock); heap_close(maprel, AccessShareLock); - if (ndicts > 0) + if (maxtokentype > 0) { - /* save the last token type's dictionaries */ - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - /* and save the overall map */ + /* save the overall map */ entry->lenmap = maxtokentype + 1; - entry->map = (ListDictionary *) + entry->map = (TSMapElement * *) MemoryContextAlloc(CacheMemoryContext, - sizeof(ListDictionary) * entry->lenmap); - memcpy(entry->map, maplists, - sizeof(ListDictionary) * entry->lenmap); + sizeof(TSMapElement *) * entry->lenmap); + memcpy(entry->map, mapconfigs, + sizeof(TSMapElement *) * entry->lenmap); } entry->isvalid = true; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d066f4f..c5cb3c6 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -14223,15 +14223,29 @@ dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo) PQclear(res); resetPQExpBuffer(query); - appendPQExpBuffer(query, - "SELECT\n" - " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" - " m.mapdict::pg_catalog.regdictionary AS dictname\n" - "FROM pg_catalog.pg_ts_config_map AS m\n" - "WHERE m.mapcfg = '%u'\n" - "ORDER BY m.mapcfg, m.maptokentype, m.mapseqno", - cfginfo->cfgparser, cfginfo->dobj.catId.oid); + + if (fout->remoteVersion >= 110000) + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); + else + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " m.mapdict::pg_catalog.regdictionary AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype, m.mapseqno\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 0c3be1f..729242e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -4646,25 +4646,41 @@ describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname, initPQExpBuffer(&buf); - printfPQExpBuffer(&buf, - "SELECT\n" - " ( SELECT t.alias FROM\n" - " pg_catalog.ts_token_type(c.cfgparser) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" - " pg_catalog.btrim(\n" - " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" - " FROM pg_catalog.pg_ts_config_map AS mm\n" - " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" - " ORDER BY mapcfg, maptokentype, mapseqno\n" - " ) :: pg_catalog.text,\n" - " '{}') AS \"%s\"\n" - "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" - "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" - "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" - "ORDER BY 1;", - gettext_noop("Token"), - gettext_noop("Dictionaries"), - oid); + if (pset.sversion >= 110000) + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + else + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " pg_catalog.btrim(\n" + " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" + " FROM pg_catalog.pg_ts_config_map AS mm\n" + " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" + " ORDER BY mapcfg, maptokentype, mapseqno\n" + " ) :: pg_catalog.text,\n" + " '{}') AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + res = PSQLexec(buf.data); termPQExpBuffer(&buf); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 7dd9d10..589bce4 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -262,7 +262,7 @@ DECLARE_UNIQUE_INDEX(pg_ts_config_cfgname_index, 3608, on pg_ts_config using btr DECLARE_UNIQUE_INDEX(pg_ts_config_oid_index, 3712, on pg_ts_config using btree(oid oid_ops)); #define TSConfigOidIndexId 3712 -DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops, mapseqno int4_ops)); +DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops)); #define TSConfigMapIndexId 3609 DECLARE_UNIQUE_INDEX(pg_ts_dict_dictname_index, 3604, on pg_ts_dict using btree(dictname name_ops, dictnamespace oid_ops)); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 9bf20c0..bd9549a 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4988,6 +4988,12 @@ DESCR("transform jsonb to tsvector"); DATA(insert OID = 4212 ( to_tsvector PGNSP PGUID 12 100 0 0 0 f f f t f i s 2 0 3614 "3734 114" _null_ _null_ _null_ _null_ _null_ json_to_tsvector_byid _null_ _null_ _null_ )); DESCR("transform json to tsvector"); +DATA(insert OID = 8891 ( dictionary_mapping_to_text PGNSP PGUID 12 100 0 0 0 f f f t f s s 2 0 25 "26 23" _null_ _null_ _null_ _null_ _null_ dictionary_mapping_to_text _null_ _null_ _null_ )); +DESCR("returns text representation of dictionary configuration map"); + +DATA(insert OID = 8892 ( ts_debug PGNSP PGUID 12 100 1 0 0 f f f t t s s 2 0 2249 "3734 25" "{3734,25,25,25,25,3770,25,25,1009}" "{i,i,o,o,o,o,o,o,o}" "{cfgId,inputText,alias,description,token,dictionaries,configuration,command,lexemes}" _null_ _null_ ts_debug _null_ _null_ _null_)); +DESCR("debug function for text search configuration"); + DATA(insert OID = 3752 ( tsvector_update_trigger PGNSP PGUID 12 1 0 0 0 f f f f f v s 0 0 2279 "" _null_ _null_ _null_ _null_ _null_ tsvector_update_trigger_byid _null_ _null_ _null_ )); DESCR("trigger for automatic update of tsvector column"); DATA(insert OID = 3753 ( tsvector_update_trigger_column PGNSP PGUID 12 1 0 0 0 f f f f f v s 0 0 2279 "" _null_ _null_ _null_ _null_ _null_ tsvector_update_trigger_bycolumn _null_ _null_ _null_ )); diff --git a/src/include/catalog/pg_ts_config_map.h b/src/include/catalog/pg_ts_config_map.h index a3d9e3f..6bcd44a 100644 --- a/src/include/catalog/pg_ts_config_map.h +++ b/src/include/catalog/pg_ts_config_map.h @@ -22,6 +22,7 @@ #define PG_TS_CONFIG_MAP_H #include "catalog/genbki.h" +#include "utils/jsonb.h" /* ---------------- * pg_ts_config_map definition. cpp turns this into @@ -30,49 +31,109 @@ */ #define TSConfigMapRelationId 3603 +/* + * Create a typedef in order to use same type name in + * generated DB initialization script and C source code + */ +typedef Jsonb jsonb; + CATALOG(pg_ts_config_map,3603) BKI_WITHOUT_OIDS { Oid mapcfg; /* OID of configuration owning this entry */ int32 maptokentype; /* token type from parser */ - int32 mapseqno; /* order in which to consult dictionaries */ - Oid mapdict; /* dictionary to consult */ + jsonb mapdicts; /* dictionary map Jsonb representation */ } FormData_pg_ts_config_map; typedef FormData_pg_ts_config_map *Form_pg_ts_config_map; +/* + * Element of the mapping expression tree + */ +typedef struct TSMapElement +{ + int type; /* Type of the element */ + union + { + struct TSMapExpression *objectExpression; + struct TSMapCase *objectCase; + Oid objectDictionary; + void *object; + } value; + struct TSMapElement *parent; /* Parent in the expression tree */ +} TSMapElement; + +/* + * Representation of expression with operator and two operands + */ +typedef struct TSMapExpression +{ + int operator; + TSMapElement *left; + TSMapElement *right; +} TSMapExpression; + +/* + * Representation of CASE structure inside database + */ +typedef struct TSMapCase +{ + TSMapElement *condition; + TSMapElement *command; + TSMapElement *elsebranch; + bool match; /* If false, NO MATCH is used */ +} TSMapCase; + /* ---------------- - * compiler constants for pg_ts_config_map + * Compiler constants for pg_ts_config_map * ---------------- */ -#define Natts_pg_ts_config_map 4 +#define Natts_pg_ts_config_map 3 #define Anum_pg_ts_config_map_mapcfg 1 #define Anum_pg_ts_config_map_maptokentype 2 -#define Anum_pg_ts_config_map_mapseqno 3 -#define Anum_pg_ts_config_map_mapdict 4 +#define Anum_pg_ts_config_map_mapdicts 3 + +/* ---------------- + * Dictionary map operators + * ---------------- + */ +#define TSMAP_OP_MAP 1 +#define TSMAP_OP_UNION 2 +#define TSMAP_OP_EXCEPT 3 +#define TSMAP_OP_INTERSECT 4 +#define TSMAP_OP_COMMA 5 + +/* ---------------- + * TSMapElement object types + * ---------------- + */ +#define TSMAP_EXPRESSION 1 +#define TSMAP_CASE 2 +#define TSMAP_DICTIONARY 3 +#define TSMAP_KEEP 4 /* ---------------- * initial contents of pg_ts_config_map * ---------------- */ -DATA(insert ( 3748 1 1 3765 )); -DATA(insert ( 3748 2 1 3765 )); -DATA(insert ( 3748 3 1 3765 )); -DATA(insert ( 3748 4 1 3765 )); -DATA(insert ( 3748 5 1 3765 )); -DATA(insert ( 3748 6 1 3765 )); -DATA(insert ( 3748 7 1 3765 )); -DATA(insert ( 3748 8 1 3765 )); -DATA(insert ( 3748 9 1 3765 )); -DATA(insert ( 3748 10 1 3765 )); -DATA(insert ( 3748 11 1 3765 )); -DATA(insert ( 3748 15 1 3765 )); -DATA(insert ( 3748 16 1 3765 )); -DATA(insert ( 3748 17 1 3765 )); -DATA(insert ( 3748 18 1 3765 )); -DATA(insert ( 3748 19 1 3765 )); -DATA(insert ( 3748 20 1 3765 )); -DATA(insert ( 3748 21 1 3765 )); -DATA(insert ( 3748 22 1 3765 )); +DATA(insert ( 3748 1 "[3765]" )); +DATA(insert ( 3748 2 "[3765]" )); +DATA(insert ( 3748 3 "[3765]" )); +DATA(insert ( 3748 4 "[3765]" )); +DATA(insert ( 3748 5 "[3765]" )); +DATA(insert ( 3748 6 "[3765]" )); +DATA(insert ( 3748 7 "[3765]" )); +DATA(insert ( 3748 8 "[3765]" )); +DATA(insert ( 3748 9 "[3765]" )); +DATA(insert ( 3748 10 "[3765]" )); +DATA(insert ( 3748 11 "[3765]" )); +DATA(insert ( 3748 15 "[3765]" )); +DATA(insert ( 3748 16 "[3765]" )); +DATA(insert ( 3748 17 "[3765]" )); +DATA(insert ( 3748 18 "[3765]" )); +DATA(insert ( 3748 19 "[3765]" )); +DATA(insert ( 3748 20 "[3765]" )); +DATA(insert ( 3748 21 "[3765]" )); +DATA(insert ( 3748 22 "[3765]" )); #endif /* PG_TS_CONFIG_MAP_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fce4802..1d3896d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -385,6 +385,9 @@ typedef enum NodeTag T_CreateEnumStmt, T_CreateRangeStmt, T_AlterEnumStmt, + T_DictMapExprElem, + T_DictMapElem, + T_DictMapCase, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, T_CreateFdwStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 699fa77..6103b12 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3434,6 +3434,50 @@ typedef enum AlterTSConfigType ALTER_TSCONFIG_DROP_MAPPING } AlterTSConfigType; +/* + * TS Configuration expression tree element's types + */ +typedef enum DictMapElemType +{ + DICT_MAP_CASE, + DICT_MAP_EXPRESSION, + DICT_MAP_KEEP, + DICT_MAP_DICTIONARY +} DictMapElemType; + +/* + * TS Configuration expression tree abstract element + */ +typedef struct DictMapElem +{ + NodeTag type; + int8 kind; /* See DictMapElemType */ + void *data; /* Type should be detected by kind value */ +} DictMapElem; + +/* + * TS Configuration expression tree element with operator and operands + */ +typedef struct DictMapExprElem +{ + NodeTag type; + DictMapElem *left; + DictMapElem *right; + int8 oper; +} DictMapExprElem; + +/* + * TS Configuration expression tree CASE element + */ +typedef struct DictMapCase +{ + NodeTag type; + struct DictMapElem *condition; + struct DictMapElem *command; + struct DictMapElem *elsebranch; + bool match; +} DictMapCase; + typedef struct AlterTSConfigurationStmt { NodeTag type; @@ -3446,6 +3490,7 @@ typedef struct AlterTSConfigurationStmt */ List *tokentype; /* list of Value strings */ List *dicts; /* list of list of Value strings */ + DictMapElem *dict_map; /* tree of the mapping expression */ bool override; /* if true - remove old variant */ bool replace; /* if true - replace dictionary by another */ bool missing_ok; /* for DROP - skip error if missing? */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 4dff55a..3371f28 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -220,6 +220,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("keep", KEEP, RESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) @@ -242,6 +243,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD) +PG_KEYWORD("map", MAP, UNRESERVED_KEYWORD) PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD) diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h index 410f1d5..4633dd7 100644 --- a/src/include/tsearch/ts_cache.h +++ b/src/include/tsearch/ts_cache.h @@ -14,6 +14,7 @@ #define TS_CACHE_H #include "utils/guc.h" +#include "catalog/pg_ts_config_map.h" /* @@ -66,6 +67,7 @@ typedef struct { int len; Oid *dictIds; + int32 *dictOptions; } ListDictionary; typedef struct @@ -77,7 +79,7 @@ typedef struct Oid prsId; int lenmap; - ListDictionary *map; + TSMapElement **map; } TSConfigCacheEntry; diff --git a/src/include/tsearch/ts_configmap.h b/src/include/tsearch/ts_configmap.h new file mode 100644 index 0000000..79e6180 --- /dev/null +++ b/src/include/tsearch/ts_configmap.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.h + * internal representation of text search configuration and utilities for it + * + * Copyright (c) 1998-2018, PostgreSQL Global Development Group + * + * src/include/tsearch/ts_utils.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PG_TS_CONFIGMAP_H_ +#define _PG_TS_CONFIGMAP_H_ + +#include "utils/jsonb.h" +#include "catalog/pg_ts_config_map.h" + +/* + * Configuration storage functions + * Provide interface to convert ts_configuration into JSONB and vice versa + */ + +/* Convert TSMapElement structure into JSONB */ +extern Jsonb *TSMapToJsonb(TSMapElement *config); + +/* Extract TSMapElement from JSONB formated data */ +extern TSMapElement *JsonbToTSMap(Jsonb *json); +/* Replace all occurances of oldDict by newDict */ +extern void TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict); + +/* Move rule list into specified memory context */ +extern TSMapElement *TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context); +/* Free all nodes of the rule list */ +extern void TSMapElementFree(TSMapElement *element); + +/* Print map in human-readable format */ +extern void TSMapPrintElement(TSMapElement *config, StringInfo result); + +/* Print dictionary name for a given Oid */ +extern void TSMapPrintDictName(Oid dictId, StringInfo result); + +/* Return all dictionaries used in config */ +extern Oid *TSMapGetDictionaries(TSMapElement *config); + +/* Do a deep comparison of two TSMapElements. Doesn't check parents of elements */ +extern bool TSMapElementEquals(TSMapElement *a, TSMapElement *b); + +#endif /* _PG_TS_CONFIGMAP_H_ */ diff --git a/src/include/tsearch/ts_public.h b/src/include/tsearch/ts_public.h index 0b7a5aa..d970eec 100644 --- a/src/include/tsearch/ts_public.h +++ b/src/include/tsearch/ts_public.h @@ -115,6 +115,7 @@ typedef struct #define TSL_ADDPOS 0x01 #define TSL_PREFIX 0x02 #define TSL_FILTER 0x04 +#define TSL_MULTI 0x08 /* * Struct for supporting complex dictionaries like thesaurus. diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index d56c70c..08c2674 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -1089,14 +1089,6 @@ WHERE mapcfg != 0 AND ------+-------- (0 rows) -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); - ctid | mapdict -------+--------- -(0 rows) - SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/expected/tsdicts.out b/src/test/regress/expected/tsdicts.out index 0c1d7c7..04ac38b 100644 --- a/src/test/regress/expected/tsdicts.out +++ b/src/test/regress/expected/tsdicts.out @@ -420,6 +420,105 @@ SELECT ts_lexize('thesaurus', 'one'); {1} (1 row) +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; +SELECT to_tsvector('english_union', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_union', 'books'); + to_tsvector +-------------------- + 'book':1 'books':1 +(1 row) + +SELECT to_tsvector('english_union', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; +SELECT to_tsvector('english_intersect', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_intersect', 'books'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_intersect', 'booking'); + to_tsvector +------------- + +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; +SELECT to_tsvector('english_except', 'book'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_except', 'books'); + to_tsvector +------------- + 'books':1 +(1 row) + +SELECT to_tsvector('english_except', 'booking'); + to_tsvector +------------- + 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; +SELECT to_tsvector('english_branches', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'books'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -580,6 +679,153 @@ SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a 'card':3,10 'invit':2,9 'like':6 'look':5 'order':1,8 (1 row) +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +-------------------------------------------------------------------------------------- + '1987a':6 'mysteri':2 'mysterious':2 'of':4 'ring':3 'rings':3 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +--------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +----------------------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 'supernova':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +------------------------------------------------------------------------ + '1987a':6 'mysterious':2 'of':4 'rings':3 'sn':5 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------------------- + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN MATCH THEN simple UNION thesaurus+ + | ELSE simple + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------------------ + '12':1 'one':1 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +----------------------------------- + '123':1 'one':1 'three':3 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two four'); + to_tsvector +--------------------------------- + '12':1 'four':3 'one':1 'two':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------ + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN NO MATCH THEN simple+ + | ELSE thesaurus + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +------------------ + '12':1 'books':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +----------------- + '12':1 'book':2 +(1 row) + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index d63fb12..c0e9fc5 100644 --- a/src/test/regress/expected/tsearch.out +++ b/src/test/regress/expected/tsearch.out @@ -36,11 +36,11 @@ WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -----+--------- (0 rows) -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; - mapcfg | maptokentype | mapseqno ---------+--------------+---------- +WHERE mapcfg = 0; + mapcfg | maptokentype +--------+-------------- (0 rows) -- Look for pg_ts_config_map entries that aren't one of parser's token types @@ -51,8 +51,8 @@ RIGHT JOIN pg_ts_config_map AS m ON (tt.cfgid=m.mapcfg AND tt.tokid=m.maptokentype) WHERE tt.cfgid IS NULL OR tt.tokid IS NULL; - cfgid | tokid | mapcfg | maptokentype | mapseqno | mapdict --------+-------+--------+--------------+----------+--------- + cfgid | tokid | mapcfg | maptokentype | mapdicts +-------+-------+--------+--------------+---------- (0 rows) -- test basic text search behavior without indexes, then with @@ -567,55 +567,55 @@ SELECT length(to_tsvector('english', '345 [email protected] '' http://www.com/ http://ae -- ts_debug SELECT * from ts_debug('english', '<myns:foo-bar_baz.blurfl>abc&nm1;def©ghiõjkl</myns:foo-bar_baz.blurfl>'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+----------------------------+----------------+--------------+--------- - tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | - asciiword | Word, all ASCII | abc | {english_stem} | english_stem | {abc} - entity | XML entity | &nm1; | {} | | - asciiword | Word, all ASCII | def | {english_stem} | english_stem | {def} - entity | XML entity | © | {} | | - asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | {ghi} - entity | XML entity | õ | {} | | - asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | {jkl} - tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+----------------------------+----------------+---------------+--------------+--------- + tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | | + asciiword | Word, all ASCII | abc | {english_stem} | english_stem | english_stem | {abc} + entity | XML entity | &nm1; | {} | | | + asciiword | Word, all ASCII | def | {english_stem} | english_stem | english_stem | {def} + entity | XML entity | © | {} | | | + asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | english_stem | {ghi} + entity | XML entity | õ | {} | | | + asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | english_stem | {jkl} + tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | | (9 rows) -- check parsing of URLs SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx</span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------------------+--------------+------------+------------------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | {www.harewoodsolutions.co.uk/press.aspx} - host | Host | www.harewoodsolutions.co.uk | {simple} | simple | {www.harewoodsolutions.co.uk} - url_path | URL path | /press.aspx | {simple} | simple | {/press.aspx} - tag | XML tag | </span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------------------+--------------+---------------+---------+------------------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | simple | {www.harewoodsolutions.co.uk/press.aspx} + host | Host | www.harewoodsolutions.co.uk | {simple} | simple | simple | {www.harewoodsolutions.co.uk} + url_path | URL path | /press.aspx | {simple} | simple | simple | {/press.aspx} + tag | XML tag | </span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw<span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------+--------------+------------+------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | {aew.wer0c.ewr/id?ad=qwe&dw} - host | Host | aew.wer0c.ewr | {simple} | simple | {aew.wer0c.ewr} - url_path | URL path | /id?ad=qwe&dw | {simple} | simple | {/id?ad=qwe&dw} - tag | XML tag | <span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------+--------------+---------------+---------+------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | simple | {aew.wer0c.ewr/id?ad=qwe&dw} + host | Host | aew.wer0c.ewr | {simple} | simple | simple | {aew.wer0c.ewr} + url_path | URL path | /id?ad=qwe&dw | {simple} | simple | simple | {/id?ad=qwe&dw} + tag | XML tag | <span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://5aew.werc.ewr:8100/?'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------+--------------+------------+------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | {5aew.werc.ewr:8100/?} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /? | {simple} | simple | {/?} + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------+--------------+---------------+---------+------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | simple | {5aew.werc.ewr:8100/?} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /? | {simple} | simple | simple | {/?} (4 rows) SELECT * from ts_debug('english', '5aew.werc.ewr:8100/?xx'); - alias | description | token | dictionaries | dictionary | lexemes -----------+-------------+------------------------+--------------+------------+-------------------------- - url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | {5aew.werc.ewr:8100/?xx} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /?xx | {simple} | simple | {/?xx} + alias | description | token | dictionaries | configuration | command | lexemes +----------+-------------+------------------------+--------------+---------------+---------+-------------------------- + url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | simple | {5aew.werc.ewr:8100/?xx} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /?xx | {simple} | simple | simple | {/?xx} (3 rows) SELECT token, alias, diff --git a/src/test/regress/sql/oidjoins.sql b/src/test/regress/sql/oidjoins.sql index 656cace..4e6730f 100644 --- a/src/test/regress/sql/oidjoins.sql +++ b/src/test/regress/sql/oidjoins.sql @@ -545,10 +545,6 @@ SELECT ctid, mapcfg FROM pg_catalog.pg_ts_config_map fk WHERE mapcfg != 0 AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_config pk WHERE pk.oid = fk.mapcfg); -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/sql/tsdicts.sql b/src/test/regress/sql/tsdicts.sql index 1633c0d..8662820 100644 --- a/src/test/regress/sql/tsdicts.sql +++ b/src/test/regress/sql/tsdicts.sql @@ -117,6 +117,57 @@ CREATE TEXT SEARCH DICTIONARY thesaurus ( SELECT ts_lexize('thesaurus', 'one'); +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; + +SELECT to_tsvector('english_union', 'book'); +SELECT to_tsvector('english_union', 'books'); +SELECT to_tsvector('english_union', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; + +SELECT to_tsvector('english_intersect', 'book'); +SELECT to_tsvector('english_intersect', 'books'); +SELECT to_tsvector('english_intersect', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; + +SELECT to_tsvector('english_except', 'book'); +SELECT to_tsvector('english_except', 'books'); +SELECT to_tsvector('english_except', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; + +SELECT to_tsvector('english_branches', 'book'); +SELECT to_tsvector('english_branches', 'books'); +SELECT to_tsvector('english_branches', 'booking'); + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -189,6 +240,43 @@ SELECT to_tsvector('thesaurus_tst', 'one postgres one two one two three one'); SELECT to_tsvector('thesaurus_tst', 'Supernovae star is very new star and usually called supernovae (abbreviation SN)'); SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a tickets'); +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two four'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index 1c8520b..6f8af63 100644 --- a/src/test/regress/sql/tsearch.sql +++ b/src/test/regress/sql/tsearch.sql @@ -26,9 +26,9 @@ SELECT oid, cfgname FROM pg_ts_config WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; +WHERE mapcfg = 0; -- Look for pg_ts_config_map entries that aren't one of parser's token types SELECT * FROM ^ permalink raw reply [nested|flat] 265+ messages in thread
* Re: Flexible configuration for full-text search @ 2018-04-05 14:26 Teodor Sigaev <[email protected]> parent: Aleksandr Parfenov <[email protected]> 0 siblings, 2 replies; 265+ messages in thread From: Teodor Sigaev @ 2018-04-05 14:26 UTC (permalink / raw) To: Aleksandr Parfenov <[email protected]>; Aleksander Alekseev <[email protected]>; +Cc: [email protected] Some notices: 0) patch conflicts with last changes in gram.y, conflicts are trivial. 1) jsonb in catalog. I'm ok with it, any opinions? 2) pg_ts_config_map.h, "jsonb mapdicts" isn't decorated with #ifdef CATALOG_VARLEN like other varlena columns in catalog. It it's right, pls, explain and add comment. 3) I see changes in pg_catalog, including drop column, change its type, change index, change function etc. Did you pay attention to pg_upgrade? I don't see it in patch. 4) Seems, it could work: ALTER TEXT SEARCH CONFIGURATION russian ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part WITH english_stem union (russian_stem, simple); ^^^^^^^^^^^^^^^^^^^^^ simple way instead of WITH english_stem union (case russian_stem when match then keep else simple end); 4) Initial approach suggested to distinguish three state of dictionary result: null (unknown word), stopword and usual word. Now only two, we lost possibility to catch stopwords. One of way to use stopwrods is: let we have to identical fts configurations, except one skips stopwords and another doesn't. Second configuration is used for indexing, and first one for search by default. But if we can't find anything ('to be or to be' - phrase contains stopwords only) then we can use second configuration. For now, we need to keep two variant of each dictionary - with and without stopwords. But if it's possible to distinguish stop and nonstop words in configuration then we don't need to have duplicated dictionaries. Aleksandr Parfenov wrote: > On Fri, 30 Mar 2018 14:43:30 +0000 > Aleksander Alekseev <[email protected]> wrote: > >> The following review has been posted through the commitfest >> application: make installcheck-world: tested, passed >> Implements feature: tested, passed >> Spec compliant: tested, passed >> Documentation: tested, passed >> >> LGTM. >> >> The new status of this patch is: Ready for Committer > > It seems that after d204ef6 (MERGE SQL Command) in master the patch > doesn't apply due to a conflict in keywords lists (grammar and header). > The new version of the patch without conflicts is attached. > -- Teodor Sigaev E-mail: [email protected] WWW: http://www.sigaev.ru/ ^ permalink raw reply [nested|flat] 265+ messages in thread
* Re: Flexible configuration for full-text search @ 2018-04-05 18:37 Andres Freund <[email protected]> parent: Teodor Sigaev <[email protected]> 1 sibling, 0 replies; 265+ messages in thread From: Andres Freund @ 2018-04-05 18:37 UTC (permalink / raw) To: Teodor Sigaev <[email protected]>; +Cc: Aleksandr Parfenov <[email protected]>; Aleksander Alekseev <[email protected]>; [email protected] Hi, On 2018-04-05 17:26:10 +0300, Teodor Sigaev wrote: > Some notices: > > 0) patch conflicts with last changes in gram.y, conflicts are trivial. > > 1) jsonb in catalog. I'm ok with it, any opinions? > > 2) pg_ts_config_map.h, "jsonb mapdicts" isn't decorated with #ifdef > CATALOG_VARLEN like other varlena columns in catalog. It it's right, pls, > explain and add comment. > > 3) I see changes in pg_catalog, including drop column, change its type, > change index, change function etc. Did you pay attention to pg_upgrade? I > don't see it in patch. > > 4) Seems, it could work: > ALTER TEXT SEARCH CONFIGURATION russian > ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, > word, hword, hword_part > WITH english_stem union (russian_stem, simple); > ^^^^^^^^^^^^^^^^^^^^^ simple way instead of > WITH english_stem union (case russian_stem when match then keep else simple end); > > 4) Initial approach suggested to distinguish three state of dictionary > result: null (unknown word), stopword and usual word. Now only two, we lost > possibility to catch stopwords. One of way to use stopwrods is: let we have > to identical fts configurations, except one skips stopwords and another > doesn't. Second configuration is used for indexing, and first one for search > by default. But if we can't find anything ('to be or to be' - phrase > contains stopwords only) then we can use second configuration. For now, we > need to keep two variant of each dictionary - with and without stopwords. > But if it's possible to distinguish stop and nonstop words in configuration > then we don't need to have duplicated dictionaries. Just to be clear: I object to attempting to merge this into v11. This introduces new user interface, arrived late in the development cycle, and hasn't seen that much review. Not something that should be merged two minutes before midnight. I think it's good to continue reviewing, don't get me wrong. - Andres ^ permalink raw reply [nested|flat] 265+ messages in thread
* Re: Flexible configuration for full-text search @ 2018-04-06 07:51 Aleksandr Parfenov <[email protected]> parent: Teodor Sigaev <[email protected]> 1 sibling, 1 reply; 265+ messages in thread From: Aleksandr Parfenov @ 2018-04-06 07:51 UTC (permalink / raw) To: Teodor Sigaev <[email protected]>; +Cc: Aleksander Alekseev <[email protected]>; [email protected] On Thu, 5 Apr 2018 17:26:10 +0300 Teodor Sigaev <[email protected]> wrote: > Some notices: > > 0) patch conflicts with last changes in gram.y, conflicts are trivial. Yes, due to commits with MERGE command with changes in gram.y there were some conflicts. > 2) pg_ts_config_map.h, "jsonb mapdicts" isn't decorated with > #ifdef CATALOG_VARLEN like other varlena columns in catalog. It it's > right, pls, explain and add comment. Since there is only one varlena column it is safe to use it directly. I add a related comment about it. > 3) I see changes in pg_catalog, including drop column, change its > type, change index, change function etc. Did you pay attention to > pg_upgrade? I don't see it in patch. The full-text search configuration is migrated via FTS commands such as CREATE TEXT SEARCH CONFIGURATION. The pg_upgrade uses pg_dump to create a dump of this part of the catalog where dictionary_mapping_to_text is used to get a textual representation of the FTS configuration. Correct me if I'm wrong. > 4) Seems, it could work: > ALTER TEXT SEARCH CONFIGURATION russian > ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, > word, hword, hword_part > WITH english_stem union (russian_stem, simple); > ^^^^^^^^^^^^^^^^^^^^^ simple way > instead of WITH english_stem union (case russian_stem when match then > keep else simple end); I add such ability since it was just a little fix in grammar. I also add tests for this kind of configurations. The test is a bit synthetic because I used a synonym dictionary as one which doesn't accept some input. > 4) Initial approach suggested to distinguish three state of > dictionary result: null (unknown word), stopword and usual word. Now > only two, we lost possibility to catch stopwords. One of way to use > stopwrods is: let we have to identical fts configurations, except one > skips stopwords and another doesn't. Second configuration is used for > indexing, and first one for search by default. But if we can't find > anything ('to be or to be' - phrase contains stopwords only) then we > can use second configuration. For now, we need to keep two variant of > each dictionary - with and without stopwords. But if it's possible to > distinguish stop and nonstop words in configuration then we don't > need to have duplicated dictionaries. With the proposed way to configure it is possible to create a special dictionary only for stopword checking and use it at decision-making time. For example, we can create dictionary english_stopword which will return word itself in case of stopword and NULL otherwise. With such dictionary we create a configuration: ALTER TEXT SEARCH CONFIGURATION test_cfg ALTER MAPPING FOR asciiword, word WITH CASE english_stopword WHEN NO MATCH THEN english_hunspell END; In described example, english_hunspell can be implemented without processing of stopwords at all and we can divide stopword processing and processing of other words into separate dictionaries. The key point of the patch is to process stopwords the same way as others at the level of the PostgreSQL internals and give users an instrument to process them in a special way via configurations. -- Aleksandr Parfenov Postgres Professional: http://www.postgrespro.com Russian Postgres Company Attachments: [text/x-patch] 0001-flexible-fts-configuration-v11.patch (177.3K, ../../20180406105138.72ed468c@asp437-manjaro/2-0001-flexible-fts-configuration-v11.patch) download | inline diff: diff --git a/contrib/unaccent/expected/unaccent.out b/contrib/unaccent/expected/unaccent.out index b93105e9c7..37b9337635 100644 --- a/contrib/unaccent/expected/unaccent.out +++ b/contrib/unaccent/expected/unaccent.out @@ -61,3 +61,14 @@ SELECT ts_lexize('unaccent', ' {����} (1 row) +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; +SELECT to_tsvector('unaccent', 'foobar ����� ����'); + to_tsvector +------------------------------ + 'foobar':1 '�����':2 '���':3 +(1 row) + diff --git a/contrib/unaccent/sql/unaccent.sql b/contrib/unaccent/sql/unaccent.sql index 310213994f..6ce21cdfcd 100644 --- a/contrib/unaccent/sql/unaccent.sql +++ b/contrib/unaccent/sql/unaccent.sql @@ -2,7 +2,6 @@ CREATE EXTENSION unaccent; -- must have a UTF8 database SELECT getdatabaseencoding(); - SET client_encoding TO 'KOI8'; SELECT unaccent('foobar'); @@ -16,3 +15,12 @@ SELECT unaccent('unaccent', ' SELECT ts_lexize('unaccent', 'foobar'); SELECT ts_lexize('unaccent', '����'); SELECT ts_lexize('unaccent', '����'); + +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); + +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; + +SELECT to_tsvector('unaccent', 'foobar ����� ����'); diff --git a/doc/src/sgml/ref/alter_tsconfig.sgml b/doc/src/sgml/ref/alter_tsconfig.sgml index ebe0b94b27..ecc37044a9 100644 --- a/doc/src/sgml/ref/alter_tsconfig.sgml +++ b/doc/src/sgml/ref/alter_tsconfig.sgml @@ -21,8 +21,12 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> @@ -88,6 +92,17 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </listitem> </varlistentry> + <varlistentry> + <term><replaceable class="parameter">config</replaceable></term> + <listitem> + <para> + The dictionaries tree expression. The dictionary expression + is a triple of condition/command/else that define way to process + the text. The <literal>ELSE</literal> part is optional. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable class="parameter">old_dictionary</replaceable></term> <listitem> @@ -133,7 +148,7 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </para> </listitem> </varlistentry> - </variablelist> + </variablelist> <para> The <literal>ADD MAPPING FOR</literal> form installs a list of dictionaries to be @@ -154,6 +169,53 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </refsect1> + <refsect1> + <title>Dictionaries Map Configuration</title> + + <refsect2> + <title>Format</title> + <para> + Formally <replaceable class="parameter">config</replaceable> is one of: + </para> + <programlisting> + * dictionary_name + + * config { UNION | INTERSECT | EXCEPT | MAP } config + + * CASE config + WHEN [ NO ] MATCH THEN { KEEP | config } + [ ELSE config ] + END + </programlisting> + </refsect2> + + <refsect2> + <title>Description</title> + <para> + <replaceable class="parameter">config</replaceable> can be used + in three different formats. The most simple format is name of dictionary to + use for tokens processing. + </para> + <para> + In order to use more than one dictionary + simultaneously user should interconnect dictionaries by operators. Operators + <literal>UNION</literal>, <literal>EXCEPT</literal> and + <literal>INTERSECT</literal> have same meaning as in operations on sets. + Special operator <literal>MAP</literal> gets output of left subexpression + and uses it as an input to right subexpression. + </para> + <para> + The third format of <replaceable class="parameter">config</replaceable> is similar to + <literal>CASE/WHEN/THEN/ELSE</literal> structure. It's consists of three + replaceable parts. First one is configuration which is used to construct lexemes set + for matching condition. If the condition is triggered, the command is executed. + Use command <literal>KEEP</literal> to avoid repeating of the same + configuration in condition and command part. However, command may differ from + the condition. The <literal>ELSE</literal> branch is executed otherwise. + </para> + </refsect2> + </refsect1> + <refsect1> <title>Examples</title> @@ -167,6 +229,34 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl ALTER TEXT SEARCH CONFIGURATION my_config ALTER MAPPING REPLACE english WITH swedish; </programlisting> + + <para> + Next example shows how to analyse documents in both English and German languages. + <literal>english_hunspell</literal> and <literal>german_hunspell</literal> + return result only if a word is recognized. Otherwise, stemmer dictionaries + are used to process a token. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + In order to combine search for both exact and processed forms the vector + should contain lexemes produced by <literal>simple</literal> for exact form + of the word as well as lexemes produced by linguistic-aware dictionary + (e.g. <literal>english_stem</literal>) for processed forms. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + </refsect1> <refsect1> diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 610b7bf033..1253b41f53 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -732,10 +732,11 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); The <function>to_tsvector</function> function internally calls a parser which breaks the document text into tokens and assigns a type to each token. For each token, a list of - dictionaries (<xref linkend="textsearch-dictionaries"/>) is consulted, - where the list can vary depending on the token type. The first dictionary - that <firstterm>recognizes</firstterm> the token emits one or more normalized - <firstterm>lexemes</firstterm> to represent the token. For example, + condition/command pairs is consulted, where the list can vary depending + on the token type, condition and command are expressions on dictionaries + with matching clause in condition(<xref linkend="textsearch-dictionaries"/>). + The first command combined with true-resulted condition emits one or more normalized + <firstterm>lexemes</firstterm> to represent the token. For example, <literal>rats</literal> became <literal>rat</literal> because one of the dictionaries recognized that the word <literal>rats</literal> is a plural form of <literal>rat</literal>. Some words are recognized as @@ -743,7 +744,7 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); causes them to be ignored since they occur too frequently to be useful in searching. In our example these are <literal>a</literal>, <literal>on</literal>, and <literal>it</literal>. - If no dictionary in the list recognizes the token then it is also ignored. + If none of conditions is <literal>true</literal> the token is ignored. In this example that happened to the punctuation sign <literal>-</literal> because there are in fact no dictionaries assigned for its token type (<literal>Space symbols</literal>), meaning space tokens will never be @@ -2232,8 +2233,8 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h <para> a single lexeme with the <literal>TSL_FILTER</literal> flag set, to replace the original token with a new token to be passed to subsequent - dictionaries (a dictionary that does this is called a - <firstterm>filtering dictionary</firstterm>) + dictionaries in a comma-separated syntax (a dictionary that does this + is called a <firstterm>filtering dictionary</firstterm>) </para> </listitem> <listitem> @@ -2265,38 +2266,126 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h type that the parser can return, a separate list of dictionaries is specified by the configuration. When a token of that type is found by the parser, each dictionary in the list is consulted in turn, - until some dictionary recognizes it as a known word. If it is identified - as a stop word, or if no dictionary recognizes the token, it will be - discarded and not indexed or searched for. - Normally, the first dictionary that returns a non-<literal>NULL</literal> - output determines the result, and any remaining dictionaries are not - consulted; but a filtering dictionary can replace the given word - with a modified word, which is then passed to subsequent dictionaries. + until command is not selected based on its condition. If none of cases is + selected token will be discarded and not indexed or searched for. </para> <para> - The general rule for configuring a list of dictionaries - is to place first the most narrow, most specific dictionary, then the more - general dictionaries, finishing with a very general dictionary, like + A tree of cases is described as condition/command/else triples. Each + condition is evaluated in order to select appropriate command to generate + resulted set of lexemes. + </para> + + <para> + A condition is an expression with dictionaries used as operands and + basic set operators <literal>UNION</literal>, <literal>EXCEPT</literal>, <literal>INTERSECT</literal> + and special operator <literal>MAP</literal>. + Special operator <literal>MAP</literal> use output of left subexpression as + input for right subexpression. + </para> + + <para> + Rules to write command are same as for condition with additional keyword + <literal>KEEP</literal> considered to use the result of the condition as an output. + </para> + + <para> + A comma-separated list of dictionaries is a simplified variant of text + search configuration. Each dictionary consulted to process a token and first + non-<literal>NULL</literal> output is accepted as a processing result. + </para> + + <para> + The general rule for configuring tokens processing + is to place first case with the most narrow, most specific dictionary, then the more + general dictionaries, finishing with a very general dictionaries, like a <application>Snowball</application> stemmer or <literal>simple</literal>, which - recognizes everything. For example, for an astronomy-specific search + recognizes everything. For example, for an astronomy-specific search (<literal>astro_en</literal> configuration) one could bind token type <type>asciiword</type> (ASCII word) to a synonym dictionary of astronomical terms, a general English dictionary and a <application>Snowball</application> English - stemmer: + stemmer in comma-separated variant of mapping: + </para> <programlisting> ALTER TEXT SEARCH CONFIGURATION astro_en ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem; </programlisting> + + <para> + Another example is a configuration for both English and German languages via + operator-separated variant of mapping: + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION multi_en_de + ADD MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + This configuration provides an ability to search on collection of multilingual + documents without specifying language: + </para> + +<programlisting> +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'lack'); + id | txt +----+----------------------------------------- + 2 | with old stars and lacking gas and dust + +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'beginnen'); + id | txt +----+------------------------------------- + 1 | Das geschah zu Beginn dieses Monats +</programlisting> + + <para> + A combination of stemmer dictionary with <literal>simple</literal> one may be used to mix + search for exact form of one word and linguistic search for others. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION exact_and_linguistic + ADD MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + + <para> + In the following example a simple dictionary is used to prevent words from normalization in query. </para> +<programlisting> +WITH docs(id, txt) as (values (1, 'Supernova star'), + (2, 'Supernova stars')) +SELECT * FROM docs WHERE to_tsvector('exact_and_linguistic', txt) @@ (to_tsquery('simple', 'stars') && to_tsquery('english', 'supernovae')); + id | txt +----+----------------- + 2 | Supernova stars +</programlisting> + + <caution> + <para> + Due to lack of information about origin of each lexeme in <literal>tsvector</literal> may + lead to false-positive triggers in case of stemmed form being used as exact form in a query. + </para> + </caution> + <para> - A filtering dictionary can be placed anywhere in the list, except at the - end where it'd be useless. Filtering dictionaries are useful to partially + Filtering dictionaries are useful to partially normalize words to simplify the task of later dictionaries. For example, a filtering dictionary could be used to remove accents from accented letters, as is done by the <xref linkend="unaccent"/> module. + Filter dictionary should be placed at left of <literal>MAP</literal> + operator. If filter dictionary returns <literal>NULL</literal> it pass initial token + to the right subexpression. </para> <sect2 id="textsearch-stopwords"> @@ -2463,9 +2552,9 @@ SELECT ts_lexize('public.simple_dict','The'); <screen> SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | english_stem | {pari} CREATE TEXT SEARCH DICTIONARY my_synonym ( TEMPLATE = synonym, @@ -2477,9 +2566,12 @@ ALTER TEXT SEARCH CONFIGURATION english WITH my_synonym, english_stem; SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+---------------------------+------------+--------- - asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+---------------------------+---------------------------------------------+------------+--------- + asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | CASE my_synonym WHEN MATCH THEN KEEP +| my_synonym | {paris} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> </para> @@ -3104,6 +3196,21 @@ CREATE TEXT SEARCH DICTIONARY english_ispell ( Now we can set up the mappings for words in configuration <literal>pg</literal>: +<programlisting> +ALTER TEXT SEARCH CONFIGURATION pg + ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, + word, hword, hword_part + WITH + CASE pg_dict WHEN MATCH THEN KEEP + ELSE + CASE english_ispell WHEN MATCH THEN KEEP + ELSE english_stem + END + END; +</programlisting> + + Or use alternative comma-separated syntax: + <programlisting> ALTER TEXT SEARCH CONFIGURATION pg ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, @@ -3183,7 +3290,8 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re OUT <replaceable class="parameter">description</replaceable> <type>text</type>, OUT <replaceable class="parameter">token</replaceable> <type>text</type>, OUT <replaceable class="parameter">dictionaries</replaceable> <type>regdictionary[]</type>, - OUT <replaceable class="parameter">dictionary</replaceable> <type>regdictionary</type>, + OUT <replaceable class="parameter">configuration</replaceable> <type>text</type>, + OUT <replaceable class="parameter">command</replaceable> <type>text</type>, OUT <replaceable class="parameter">lexemes</replaceable> <type>text[]</type>) returns setof record </synopsis> @@ -3227,14 +3335,20 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re </listitem> <listitem> <para> - <replaceable>dictionary</replaceable> <type>regdictionary</type> — the dictionary - that recognized the token, or <literal>NULL</literal> if none did + <replaceable>configuration</replaceable> <type>text</type> — the + configuration defined for this token type + </para> + </listitem> + <listitem> + <para> + <replaceable>command</replaceable> <type>text</type> — the command that describes + the way the output was produced </para> </listitem> <listitem> <para> <replaceable>lexemes</replaceable> <type>text[]</type> — the lexeme(s) produced - by the dictionary that recognized the token, or <literal>NULL</literal> if + by the command selected according conditions, or <literal>NULL</literal> if none did; an empty array (<literal>{}</literal>) means it was recognized as a stop word </para> @@ -3247,32 +3361,32 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re <screen> SELECT * FROM ts_debug('english','a fat cat sat on a mat - it ate a fat rats'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | cat | {english_stem} | english_stem | {cat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | sat | {english_stem} | english_stem | {sat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | on | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | mat | {english_stem} | english_stem | {mat} - blank | Space symbols | | {} | | - blank | Space symbols | - | {} | | - asciiword | Word, all ASCII | it | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | ate | {english_stem} | english_stem | {ate} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | rats | {english_stem} | english_stem | {rat} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | cat | {english_stem} | english_stem | english_stem | {cat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | sat | {english_stem} | english_stem | english_stem | {sat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | on | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | mat | {english_stem} | english_stem | english_stem | {mat} + blank | Space symbols | | | | | + blank | Space symbols | - | | | | + asciiword | Word, all ASCII | it | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | ate | {english_stem} | english_stem | english_stem | {ate} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | rats | {english_stem} | english_stem | english_stem | {rat} </screen> </para> @@ -3298,13 +3412,22 @@ ALTER TEXT SEARCH CONFIGURATION public.english <screen> SELECT * FROM ts_debug('public.english','The Brightest supernovaes'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------------+-------------------------------+----------------+------------- - asciiword | Word, all ASCII | The | {english_ispell,english_stem} | english_ispell | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | english_ispell | {bright} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | english_stem | {supernova} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------------+-------------------------------+---------------------------------------------+------------------+------------- + asciiword | Word, all ASCII | The | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {bright} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_stem | {supernova} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> <para> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e9e188682f..34b80aea34 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -948,55 +948,14 @@ GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublicatio -- Tsearch debug function. Defined here because it'd be pretty unwieldy -- to put it into pg_proc.h -CREATE FUNCTION ts_debug(IN config regconfig, IN document text, - OUT alias text, - OUT description text, - OUT token text, - OUT dictionaries regdictionary[], - OUT dictionary regdictionary, - OUT lexemes text[]) -RETURNS SETOF record AS -$$ -SELECT - tt.alias AS alias, - tt.description AS description, - parse.token AS token, - ARRAY ( SELECT m.mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY m.mapseqno ) - AS dictionaries, - ( SELECT mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS dictionary, - ( SELECT pg_catalog.ts_lexize(mapdict, parse.token) - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS lexemes -FROM pg_catalog.ts_parse( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ), $2 - ) AS parse, - pg_catalog.ts_token_type( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ) - ) AS tt -WHERE tt.tokid = parse.tokid -$$ -LANGUAGE SQL STRICT STABLE PARALLEL SAFE; - -COMMENT ON FUNCTION ts_debug(regconfig,text) IS - 'debug function for text search configuration'; CREATE FUNCTION ts_debug(IN document text, OUT alias text, OUT description text, OUT token text, OUT dictionaries regdictionary[], - OUT dictionary regdictionary, + OUT configuration text, + OUT command text, OUT lexemes text[]) RETURNS SETOF record AS $$ diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 3a843512d1..53ee576223 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -39,9 +39,12 @@ #include "nodes/makefuncs.h" #include "parser/parse_func.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_public.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" @@ -935,11 +938,22 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + TSMapElement *mapdicts = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + Oid *dictionaryOids = TSMapGetDictionaries(mapdicts); + Oid *currentOid = dictionaryOids; - referenced.classId = TSDictionaryRelationId; - referenced.objectId = cfgmap->mapdict; - referenced.objectSubId = 0; - add_exact_object_address(&referenced, addrs); + while (*currentOid != InvalidOid) + { + referenced.classId = TSDictionaryRelationId; + referenced.objectId = *currentOid; + referenced.objectSubId = 0; + add_exact_object_address(&referenced, addrs); + + currentOid++; + } + + pfree(dictionaryOids); + TSMapElementFree(mapdicts); } systable_endscan(scan); @@ -1091,8 +1105,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied) mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid; mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype; - mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno; - mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict; + mapvalues[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(&cfgmap->mapdicts); newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls); @@ -1195,7 +1208,7 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt) relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock); /* Add or drop mappings */ - if (stmt->dicts) + if (stmt->dicts || stmt->dict_map) MakeConfigurationMapping(stmt, tup, relMap); else if (stmt->tokentype) DropConfigurationMapping(stmt, tup, relMap); @@ -1270,6 +1283,59 @@ getTokenTypes(Oid prsId, List *tokennames) return res; } +/* + * Parse parse node extracted from dictionary mapping and transform it into + * internal representation of dictionary mapping. + */ +static TSMapElement * +ParseTSMapConfig(DictMapElem *elem) +{ + TSMapElement *result = palloc0(sizeof(TSMapElement)); + + if (elem->kind == DICT_MAP_CASE) + { + TSMapCase *caseObject = palloc0(sizeof(TSMapCase)); + DictMapCase *caseASTObject = elem->data; + + caseObject->condition = ParseTSMapConfig(caseASTObject->condition); + caseObject->command = ParseTSMapConfig(caseASTObject->command); + + if (caseASTObject->elsebranch) + caseObject->elsebranch = ParseTSMapConfig(caseASTObject->elsebranch); + + caseObject->match = caseASTObject->match; + + caseObject->condition->parent = result; + caseObject->command->parent = result; + + result->type = TSMAP_CASE; + result->value.objectCase = caseObject; + } + else if (elem->kind == DICT_MAP_EXPRESSION) + { + TSMapExpression *expression = palloc0(sizeof(TSMapExpression)); + DictMapExprElem *expressionAST = elem->data; + + expression->left = ParseTSMapConfig(expressionAST->left); + expression->right = ParseTSMapConfig(expressionAST->right); + expression->operator = expressionAST->oper; + + result->type = TSMAP_EXPRESSION; + result->value.objectExpression = expression; + } + else if (elem->kind == DICT_MAP_KEEP) + { + result->value.objectExpression = NULL; + result->type = TSMAP_KEEP; + } + else if (elem->kind == DICT_MAP_DICTIONARY) + { + result->value.objectDictionary = get_ts_dict_oid(elem->data, false); + result->type = TSMAP_DICTIONARY; + } + return result; +} + /* * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING */ @@ -1286,8 +1352,9 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, Oid prsId; int *tokens, ntoken; - Oid *dictIds; - int ndict; + Oid *dictIds = NULL; + int ndict = 0; + TSMapElement *config = NULL; ListCell *c; prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser; @@ -1326,15 +1393,18 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Convert list of dictionary names to array of dict OIDs */ - ndict = list_length(stmt->dicts); - dictIds = (Oid *) palloc(sizeof(Oid) * ndict); - i = 0; - foreach(c, stmt->dicts) + if (stmt->dicts) { - List *names = (List *) lfirst(c); + ndict = list_length(stmt->dicts); + dictIds = (Oid *) palloc(sizeof(Oid) * ndict); + i = 0; + foreach(c, stmt->dicts) + { + List *names = (List *) lfirst(c); - dictIds[i] = get_ts_dict_oid(names, false); - i++; + dictIds[i] = get_ts_dict_oid(names, false); + i++; + } } if (stmt->replace) @@ -1356,6 +1426,10 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + Datum repl_val[Natts_pg_ts_config_map]; + bool repl_null[Natts_pg_ts_config_map]; + bool repl_repl[Natts_pg_ts_config_map]; + HeapTuple newtup; /* * check if it's one of target token types @@ -1379,25 +1453,21 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * replace dictionary if match */ - if (cfgmap->mapdict == dictOld) - { - Datum repl_val[Natts_pg_ts_config_map]; - bool repl_null[Natts_pg_ts_config_map]; - bool repl_repl[Natts_pg_ts_config_map]; - HeapTuple newtup; - - memset(repl_val, 0, sizeof(repl_val)); - memset(repl_null, false, sizeof(repl_null)); - memset(repl_repl, false, sizeof(repl_repl)); - - repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew); - repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true; - - newtup = heap_modify_tuple(maptup, - RelationGetDescr(relMap), - repl_val, repl_null, repl_repl); - CatalogTupleUpdate(relMap, &newtup->t_self, newtup); - } + config = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + TSMapReplaceDictionary(config, dictOld, dictNew); + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + repl_val[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); + repl_repl[Anum_pg_ts_config_map_mapdicts - 1] = true; + + newtup = heap_modify_tuple(maptup, + RelationGetDescr(relMap), + repl_val, repl_null, repl_repl); + CatalogTupleUpdate(relMap, &newtup->t_self, newtup); + pfree(config); } systable_endscan(scan); @@ -1407,24 +1477,22 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Insertion of new entries */ + config = ParseTSMapConfig(stmt->dict_map); + for (i = 0; i < ntoken; i++) { - for (j = 0; j < ndict; j++) - { - Datum values[Natts_pg_ts_config_map]; - bool nulls[Natts_pg_ts_config_map]; + Datum values[Natts_pg_ts_config_map]; + bool nulls[Natts_pg_ts_config_map]; - memset(nulls, false, sizeof(nulls)); - values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); - values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); - values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1); - values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]); + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); + values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); + values[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); - tup = heap_form_tuple(relMap->rd_att, values, nulls); - CatalogTupleInsert(relMap, tup); + tup = heap_form_tuple(relMap->rd_att, values, nulls); + CatalogTupleInsert(relMap, tup); - heap_freetuple(tup); - } + heap_freetuple(tup); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c3efca3c45..a2235c3c0c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4439,6 +4439,42 @@ _copyReassignOwnedStmt(const ReassignOwnedStmt *from) return newnode; } +static DictMapElem * +_copyDictMapElem(const DictMapElem *from) +{ + DictMapElem *newnode = makeNode(DictMapElem); + + COPY_SCALAR_FIELD(kind); + COPY_NODE_FIELD(data); + + return newnode; +} + +static DictMapExprElem * +_copyDictMapExprElem(const DictMapExprElem *from) +{ + DictMapExprElem *newnode = makeNode(DictMapExprElem); + + COPY_NODE_FIELD(left); + COPY_NODE_FIELD(right); + COPY_SCALAR_FIELD(oper); + + return newnode; +} + +static DictMapCase * +_copyDictMapCase(const DictMapCase *from) +{ + DictMapCase *newnode = makeNode(DictMapCase); + + COPY_NODE_FIELD(condition); + COPY_NODE_FIELD(command); + COPY_NODE_FIELD(elsebranch); + COPY_SCALAR_FIELD(match); + + return newnode; +} + static AlterTSDictionaryStmt * _copyAlterTSDictionaryStmt(const AlterTSDictionaryStmt *from) { @@ -5452,6 +5488,15 @@ copyObjectImpl(const void *from) case T_ReassignOwnedStmt: retval = _copyReassignOwnedStmt(from); break; + case T_DictMapExprElem: + retval = _copyDictMapExprElem(from); + break; + case T_DictMapElem: + retval = _copyDictMapElem(from); + break; + case T_DictMapCase: + retval = _copyDictMapCase(from); + break; case T_AlterTSDictionaryStmt: retval = _copyAlterTSDictionaryStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 45ceba2830..71a8f9b914 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2217,6 +2217,36 @@ _equalReassignOwnedStmt(const ReassignOwnedStmt *a, const ReassignOwnedStmt *b) return true; } +static bool +_equalDictMapElem(const DictMapElem *a, const DictMapElem *b) +{ + COMPARE_NODE_FIELD(data); + COMPARE_SCALAR_FIELD(kind); + + return true; +} + +static bool +_equalDictMapExprElem(const DictMapExprElem *a, const DictMapExprElem *b) +{ + COMPARE_NODE_FIELD(left); + COMPARE_NODE_FIELD(right); + COMPARE_SCALAR_FIELD(oper); + + return true; +} + +static bool +_equalDictMapCase(const DictMapCase *a, const DictMapCase *b) +{ + COMPARE_NODE_FIELD(condition); + COMPARE_NODE_FIELD(command); + COMPARE_NODE_FIELD(elsebranch); + COMPARE_SCALAR_FIELD(match); + + return true; +} + static bool _equalAlterTSDictionaryStmt(const AlterTSDictionaryStmt *a, const AlterTSDictionaryStmt *b) { @@ -3575,6 +3605,15 @@ equal(const void *a, const void *b) case T_ReassignOwnedStmt: retval = _equalReassignOwnedStmt(a, b); break; + case T_DictMapExprElem: + retval = _equalDictMapExprElem(a, b); + break; + case T_DictMapElem: + retval = _equalDictMapElem(a, b); + break; + case T_DictMapCase: + retval = _equalDictMapCase(a, b); + break; case T_AlterTSDictionaryStmt: retval = _equalAlterTSDictionaryStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b879358de1..84ae8b17f4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -52,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config_map.h" #include "commands/defrem.h" #include "commands/trigger.h" #include "nodes/makefuncs.h" @@ -241,6 +242,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PartitionSpec *partspec; PartitionBoundSpec *partboundspec; RoleSpec *rolespec; + DictMapElem *dmapelem; } %type <node> stmt schema_stmt @@ -310,7 +312,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); analyze_option_list analyze_option_elem %type <boolean> opt_or_replace opt_grant_grant_option opt_grant_admin_option - opt_nowait opt_if_exists opt_with_data + opt_nowait opt_if_exists opt_with_data opt_dictionary_map_no %type <ival> opt_nowait_or_skip %type <list> OptRoleList AlterOptRoleList @@ -585,6 +587,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <list> hash_partbound partbound_datum_list range_datum_list %type <defelt> hash_partbound_elem +%type <ival> dictionary_map_set_expr_operator +%type <dmapelem> dictionary_map_dict dictionary_map_command_expr_paren + dictionary_config dictionary_map_case + dictionary_map_action opt_dictionary_map_case_else + dictionary_config_comma + %type <node> merge_when_clause opt_and_condition %type <list> merge_when_list %type <node> merge_update merge_delete merge_insert @@ -650,13 +658,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JOIN - KEY + KEEP KEY LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MAP MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE @@ -10355,24 +10363,26 @@ AlterTSDictionaryStmt: ; AlterTSConfigurationStmt: - ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list + ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ADD_MAPPING; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = false; n->replace = false; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list + | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = true; n->replace = false; $$ = (Node*)n; @@ -10424,6 +10434,100 @@ any_with: WITH {} | WITH_LA {} ; +opt_dictionary_map_no: + NO { $$ = true; } + | { $$ = false; } + ; + +dictionary_config_comma: + dictionary_map_dict { $$ = $1; } + | dictionary_map_dict ',' dictionary_config_comma + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = TSMAP_OP_COMMA; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_action: + KEEP + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_KEEP; + n->data = NULL; + $$ = n; + } + | dictionary_config { $$ = $1; } + ; + +opt_dictionary_map_case_else: + ELSE dictionary_config { $$ = $2; } + | { $$ = NULL; } + ; + +dictionary_map_case: + CASE dictionary_config WHEN opt_dictionary_map_no MATCH THEN dictionary_map_action opt_dictionary_map_case_else END_P + { + DictMapCase *n = makeNode(DictMapCase); + DictMapElem *r = makeNode(DictMapElem); + + n->condition = $2; + n->command = $7; + n->elsebranch = $8; + n->match = !$4; + + r->kind = DICT_MAP_CASE; + r->data = n; + $$ = r; + } + ; + +dictionary_map_set_expr_operator: + UNION { $$ = TSMAP_OP_UNION; } + | EXCEPT { $$ = TSMAP_OP_EXCEPT; } + | INTERSECT { $$ = TSMAP_OP_INTERSECT; } + | MAP { $$ = TSMAP_OP_MAP; } + ; + +dictionary_config: + dictionary_map_command_expr_paren { $$ = $1; } + | dictionary_config dictionary_map_set_expr_operator dictionary_map_command_expr_paren + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = $2; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_command_expr_paren: + '(' dictionary_config ')' { $$ = $2; } + | dictionary_map_case { $$ = $1; } + | dictionary_config_comma { $$ = $1; } + ; + +dictionary_map_dict: + any_name + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_DICTIONARY; + n->data = $1; + $$ = n; + } + ; /***************************************************************************** * @@ -15241,6 +15345,7 @@ unreserved_keyword: | LOCK_P | LOCKED | LOGGED + | MAP | MAPPING | MATCH | MATCHED @@ -15549,6 +15654,7 @@ reserved_keyword: | INITIALLY | INTERSECT | INTO + | KEEP | LATERAL_P | LEADING | LIMIT diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile index 227468ae9e..e61ad4fa1d 100644 --- a/src/backend/tsearch/Makefile +++ b/src/backend/tsearch/Makefile @@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES)) OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \ dict_simple.o dict_synonym.o dict_thesaurus.o \ dict_ispell.o regis.o spell.o \ - to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o + to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o ts_configmap.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/tsearch/ts_configmap.c b/src/backend/tsearch/ts_configmap.c new file mode 100644 index 0000000000..714f2a8ab2 --- /dev/null +++ b/src/backend/tsearch/ts_configmap.c @@ -0,0 +1,1114 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.c + * internal representation of text search configuration and utilities for it + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/tsearch/ts_confimap.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/indexing.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_namespace.h" +#include "catalog/namespace.h" +#include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +/* + * Size selected arbitrary, based on assumption that 1024 frames of stack + * is enough for parsing of configurations + */ +#define JSONB_PARSE_STATE_STACK_SIZE 1024 + +/* + * Used during the parsing of TSMapElement from JSONB into internal + * data structures. + */ +typedef enum TSMapParseState +{ + TSMPS_WAIT_ELEMENT, + TSMPS_READ_DICT_OID, + TSMPS_READ_COMPLEX_OBJ, + TSMPS_READ_EXPRESSION, + TSMPS_READ_CASE, + TSMPS_READ_OPERATOR, + TSMPS_READ_COMMAND, + TSMPS_READ_CONDITION, + TSMPS_READ_ELSEBRANCH, + TSMPS_READ_MATCH, + TSMPS_READ_KEEP, + TSMPS_READ_LEFT, + TSMPS_READ_RIGHT +} TSMapParseState; + +/* + * Context used during JSONB parsing to construct a TSMap + */ +typedef struct TSMapJsonbParseData +{ + TSMapParseState states[JSONB_PARSE_STATE_STACK_SIZE]; /* Stack of states of + * JSONB parsing + * automaton */ + int statesIndex; /* Index of current stack frame */ + TSMapElement *element; /* Element that is in construction now */ +} TSMapJsonbParseData; + +static JsonbValue *TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState); +static TSMapElement * JsonbToTSMapElement(JsonbContainer *root); + +/* + * Print name of the namespace into StringInfo variable result + */ +static void +TSMapPrintNamespace(Oid namespaceId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_namespace namespace; + + if (false) + return; + + maprel = heap_open(NamespaceRelationId, AccessShareLock); + mapidx = index_open(NamespaceOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + namespace = (Form_pg_namespace) GETSTRUCT(maptup); + appendStringInfoString(result, namespace->nspname.data); + appendStringInfoChar(result, '.'); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print name of the dictionary into StringInfo variable result + */ +void +TSMapPrintDictName(Oid dictId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_ts_dict dict; + + if (false) + return; +maprel = heap_open(TSDictionaryRelationId, AccessShareLock); + mapidx = index_open(TSDictionaryOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dictId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + dict = (Form_pg_ts_dict) GETSTRUCT(maptup); + if (!TSDictionaryIsVisible(dictId)) + { + TSMapPrintNamespace(dict->dictnamespace, result); + } + appendStringInfoString(result, dict->dictname.data); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print the expression into StringInfo variable result + */ +static void +TSMapPrintExpression(TSMapExpression *expression, StringInfo result) +{ + + Assert(expression->left); + if (expression->left->type == TSMAP_EXPRESSION && + expression->left->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, '('); + } + TSMapPrintElement(expression->left, result); + if (expression->left->type == TSMAP_EXPRESSION && + expression->left->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, ')'); + } + + switch (expression->operator) + { + case TSMAP_OP_UNION: + appendStringInfoString(result, " UNION "); + break; + case TSMAP_OP_EXCEPT: + appendStringInfoString(result, " EXCEPT "); + break; + case TSMAP_OP_INTERSECT: + appendStringInfoString(result, " INTERSECT "); + break; + case TSMAP_OP_COMMA: + appendStringInfoString(result, ", "); + break; + case TSMAP_OP_MAP: + appendStringInfoString(result, " MAP "); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } + + Assert(expression->right); + if (expression->right->type == TSMAP_EXPRESSION && + expression->right->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, '('); + } + TSMapPrintElement(expression->right, result); + if (expression->right->type == TSMAP_EXPRESSION && + expression->right->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, ')'); + } +} + +/* + * Print the case configuration construction into StringInfo variable result + */ +static void +TSMapPrintCase(TSMapCase *caseObject, StringInfo result) +{ + appendStringInfoString(result, "CASE "); + + TSMapPrintElement(caseObject->condition, result); + + appendStringInfoString(result, " WHEN "); + if (!caseObject->match) + appendStringInfoString(result, "NO "); + appendStringInfoString(result, "MATCH THEN "); + + TSMapPrintElement(caseObject->command, result); + + if (caseObject->elsebranch != NULL) + { + appendStringInfoString(result, "\nELSE "); + TSMapPrintElement(caseObject->elsebranch, result); + } + appendStringInfoString(result, "\nEND"); +} + +/* + * Print the element into StringInfo result. + * Uses other function and serves for element type detection. + */ +void +TSMapPrintElement(TSMapElement *element, StringInfo result) +{ + switch (element->type) + { + case TSMAP_EXPRESSION: + TSMapPrintExpression(element->value.objectExpression, result); + break; + case TSMAP_DICTIONARY: + TSMapPrintDictName(element->value.objectDictionary, result); + break; + case TSMAP_CASE: + TSMapPrintCase(element->value.objectCase, result); + break; + case TSMAP_KEEP: + appendStringInfoString(result, "KEEP"); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains elements with invalid type."))); + break; + } +} + +/* + * Print the text search configuration as a text. + */ +Datum +dictionary_mapping_to_text(PG_FUNCTION_ARGS) +{ + Oid cfgOid = PG_GETARG_OID(0); + int32 tokentype = PG_GETARG_INT32(1); + StringInfo rawResult; + text *result = NULL; + TSConfigCacheEntry *cacheEntry; + + cacheEntry = lookup_ts_config_cache(cfgOid); + rawResult = makeStringInfo(); + initStringInfo(rawResult); + + if (cacheEntry->lenmap > tokentype && cacheEntry->map[tokentype] != NULL) + { + TSMapElement *element = cacheEntry->map[tokentype]; + + TSMapPrintElement(element, rawResult); + } + + result = cstring_to_text(rawResult->data); + pfree(rawResult); + PG_RETURN_TEXT_P(result); +} + +/* ---------------- + * Functions used to convert TSMap structure into JSONB representation + * ---------------- + */ + +/* + * Convert an integer value into JsonbValue + */ +static JsonbValue * +IntToJsonbValue(int intValue) +{ + char buffer[16]; + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + /* + * String size is based on limit of int capacity up to 12 chars with sign + * and NULL-character + */ + memset(buffer, 0, sizeof(char) * 12); + + pg_ltoa(intValue, buffer); + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(buffer), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1) + )); + return value; +} + +/* + * Convert a FTS configuration expression into JsonbValue + */ +static JsonbValue * +TSMapExpressionToJsonbValue(TSMapExpression *expression, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("operator"); + key.val.string.val = "operator"; + value = IntToJsonbValue(expression->operator); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("left"); + key.val.string.val = "left"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->left, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("right"); + key.val.string.val = "right"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->right, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS configuration case into JsonbValue + */ +static JsonbValue * +TSMapCaseToJsonbValue(TSMapCase *caseObject, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("condition"); + key.val.string.val = "condition"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->condition, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("command"); + key.val.string.val = "command"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->command, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + if (caseObject->elsebranch != NULL) + { + key.type = jbvString; + key.val.string.len = strlen("elsebranch"); + key.val.string.val = "elsebranch"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->elsebranch, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + } + + key.type = jbvString; + key.val.string.len = strlen("match"); + key.val.string.val = "match"; + + value = IntToJsonbValue(caseObject->match ? 1 : 0); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS KEEP command into JsonbValue + */ +static JsonbValue * +TSMapKeepToJsonbValue(JsonbParseState *jsonbState) +{ + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + value->type = jbvString; + value->val.string.len = strlen("keep"); + value->val.string.val = "keep"; + + return pushJsonbValue(&jsonbState, WJB_VALUE, value); +} + +/* + * Convert a FTS element into JsonbValue. Common point for all types of TSMapElement + */ +JsonbValue * +TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState) +{ + JsonbValue *result = NULL; + + if (element != NULL) + { + switch (element->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionToJsonbValue(element->value.objectExpression, jsonbState); + break; + case TSMAP_DICTIONARY: + result = IntToJsonbValue(element->value.objectDictionary); + break; + case TSMAP_CASE: + result = TSMapCaseToJsonbValue(element->value.objectCase, jsonbState); + break; + case TSMAP_KEEP: + result = TSMapKeepToJsonbValue(jsonbState); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Required text search configuration contains elements with invalid type."))); + break; + } + } + return result; +} + +/* + * Convert a FTS configuration into JSONB + */ +Jsonb * +TSMapToJsonb(TSMapElement *element) +{ + JsonbParseState *jsonbState = NULL; + JsonbValue *out; + Jsonb *result; + + out = TSMapElementToJsonbValue(element, jsonbState); + + result = JsonbValueToJsonb(out); + return result; +} + +/* ---------------- + * Functions used to get TSMap structure from JSONB representation + * ---------------- + */ + +/* + * Extract an integer from JsonbValue + */ +static int +JsonbValueToInt(JsonbValue *value) +{ + char *str; + + str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(value->val.numeric))); + return pg_atoi(str, sizeof(int), 0); +} + +/* + * Check is a key one of FTS configuration case fields + */ +static bool +IsTSMapCaseKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "match") == 0 || strcmp(key, "condition") == 0 || strcmp(key, "command") == 0 || strcmp(key, "elsebranch") == 0; +} + +/* + * Check is a key one of FTS configuration expression fields + */ +static bool +IsTSMapExpressionKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "operator") == 0 || strcmp(key, "left") == 0 || strcmp(key, "right") == 0; +} + +/* + * Configure parseData->element according to value (key) + */ +static void +JsonbBeginObjectKey(JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *parentElement = parseData->element; + + parseData->element = palloc0(sizeof(TSMapElement)); + parseData->element->parent = parentElement; + + /* Overwrite object-type state based on key */ + if (IsTSMapExpressionKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_EXPRESSION; + parseData->element->type = TSMAP_EXPRESSION; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapExpression)); + } + else if (IsTSMapCaseKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_CASE; + parseData->element->type = TSMAP_CASE; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapCase)); + } +} + +/* + * Process a JsonbValue inside a FTS configuration expression + */ +static void +JsonbKeyExpressionProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "operator") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_OPERATOR; + else if (strcmp(key, "left") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_LEFT; + else if (strcmp(key, "right") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_RIGHT; +} + +/* + * Process a JsonbValue inside a FTS configuration case + */ +static void +JsonbKeyCaseProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "condition") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_CONDITION; + else if (strcmp(key, "command") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_COMMAND; + else if (strcmp(key, "elsebranch") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_ELSEBRANCH; + else if (strcmp(key, "match") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_MATCH; +} + +/* + * Convert a JsonbValue into OID TSMapElement + */ +static TSMapElement * +JsonbValueToOidElement(JsonbValue *value, TSMapElement *parent) +{ + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + element->type = TSMAP_DICTIONARY; + element->value.objectDictionary = JsonbValueToInt(value); + return element; +} + +/* + * Convert a JsonbValue into string TSMapElement. + * Used for special values such as KEEP command + */ +static TSMapElement * +JsonbValueReadString(JsonbValue *value, TSMapElement *parent) +{ + char *str; + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + str = palloc0(sizeof(char) * (value->val.string.len + 1)); + memcpy(str, value->val.string.val, sizeof(char) * value->val.string.len); + + if (strcmp(str, "keep") == 0) + element->type = TSMAP_KEEP; + + pfree(str); + + return element; +} + +/* + * Process a JsonbValue object + */ +static void +JsonbProcessElement(JsonbIteratorToken r, JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *element = NULL; + + switch (r) + { + case WJB_KEY: + + /* + * Construct an TSMapElement object. At first key inside JSONB + * object a type is selected based on key. + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMPLEX_OBJ) + JsonbBeginObjectKey(value, parseData); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_EXPRESSION) + JsonbKeyExpressionProcessing(value, parseData); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CASE) + JsonbKeyCaseProcessing(value, parseData); + + break; + case WJB_BEGIN_OBJECT: + + /* + * Begin construction of new object + */ + parseData->statesIndex++; + parseData->states[parseData->statesIndex] = TSMPS_READ_COMPLEX_OBJ; + break; + case WJB_END_OBJECT: + + /* + * Save constructed object based on current state of parser + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->parent->value.objectExpression->left = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->parent->value.objectExpression->right = parseData->element; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->parent->value.objectCase->condition = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->parent->value.objectCase->command = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->parent->value.objectCase->elsebranch = parseData->element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_VALUE: + + /* + * Save a value inside constructing object + */ + if (value.type == jbvBinary) + element = JsonbToTSMapElement(value.val.binary.data); + else if (value.type == jbvString) + element = JsonbValueReadString(&value, parseData->element); + else if (value.type == jbvNumeric) + element = JsonbValueToOidElement(&value, parseData->element); + else + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->value.objectCase->condition = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->value.objectCase->command = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->value.objectCase->elsebranch = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_MATCH) + parseData->element->value.objectCase->match = JsonbValueToInt(&value) == 1 ? true : false; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_OPERATOR) + parseData->element->value.objectExpression->operator = JsonbValueToInt(&value); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->value.objectExpression->left = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->value.objectExpression->right = element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_ELEM: + + /* + * Store a simple element such as dictionary OID + */ + if (parseData->states[parseData->statesIndex] == TSMPS_WAIT_ELEMENT) + { + if (parseData->element != NULL) + parseData->element = JsonbValueToOidElement(&value, parseData->element->parent); + else + parseData->element = JsonbValueToOidElement(&value, NULL); + } + break; + default: + /* Ignore unused JSONB tokens */ + break; + } +} + +/* + * Convert a JsonbContainer into TSMapElement + */ +static TSMapElement * +JsonbToTSMapElement(JsonbContainer *root) +{ + TSMapJsonbParseData parseData; + JsonbIteratorToken r; + JsonbIterator *it; + JsonbValue val; + + parseData.statesIndex = 0; + parseData.states[parseData.statesIndex] = TSMPS_WAIT_ELEMENT; + parseData.element = NULL; + + it = JsonbIteratorInit(root); + + while ((r = JsonbIteratorNext(&it, &val, true)) != WJB_DONE) + JsonbProcessElement(r, val, &parseData); + + return parseData.element; +} + +/* + * Convert a JSONB into TSMapElement + */ +TSMapElement * +JsonbToTSMap(Jsonb *json) +{ + JsonbContainer *root = &json->root; + + return JsonbToTSMapElement(root); +} + +/* ---------------- + * Text Search Configuration Map Utils + * ---------------- + */ + +/* + * Dynamically extendable list of OIDs + */ +typedef struct OidList +{ + Oid *data; + int size; /* Size of data array. Uninitialized elements + * in data filled with InvalidOid */ +} OidList; + +/* + * Initialize a list + */ +static OidList * +OidListInit() +{ + OidList *result = palloc0(sizeof(OidList)); + + result->size = 1; + result->data = palloc0(result->size * sizeof(Oid)); + result->data[0] = InvalidOid; + return result; +} + +/* + * Add a new OID into list. If it is already stored in list, it won't be add second time. + */ +static void +OidListAdd(OidList *list, Oid oid) +{ + int i; + + /* Search for the Oid in the list */ + for (i = 0; list->data[i] != InvalidOid; i++) + if (list->data[i] == oid) + return; + + /* If not found, insert it in the end of the list */ + if (i >= list->size - 1) + { + int j; + + list->size = list->size * 2; + list->data = repalloc(list->data, sizeof(Oid) * list->size); + + for (j = i; j < list->size; j++) + list->data[j] = InvalidOid; + } + list->data[i] = oid; +} + +/* + * Get OIDs of all dictionaries used in TSMapElement. + * Used for internal recursive calls. + */ +static void +TSMapGetDictionariesInternal(TSMapElement *config, OidList *list) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapGetDictionariesInternal(config->value.objectExpression->left, list); + TSMapGetDictionariesInternal(config->value.objectExpression->right, list); + break; + case TSMAP_CASE: + TSMapGetDictionariesInternal(config->value.objectCase->command, list); + TSMapGetDictionariesInternal(config->value.objectCase->condition, list); + if (config->value.objectCase->elsebranch != NULL) + TSMapGetDictionariesInternal(config->value.objectCase->elsebranch, list); + break; + case TSMAP_DICTIONARY: + OidListAdd(list, config->value.objectDictionary); + break; + } +} + +/* + * Get OIDs of all dictionaries used in TSMapElement + */ +Oid * +TSMapGetDictionaries(TSMapElement *config) +{ + Oid *result; + OidList *list = OidListInit(); + + TSMapGetDictionariesInternal(config, list); + + result = list->data; + pfree(list); + + return result; +} + +/* + * Replace one dictionary OID with another in all instances inside a configuration + */ +void +TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapReplaceDictionary(config->value.objectExpression->left, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectExpression->right, oldDict, newDict); + break; + case TSMAP_CASE: + TSMapReplaceDictionary(config->value.objectCase->command, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectCase->condition, oldDict, newDict); + if (config->value.objectCase->elsebranch != NULL) + TSMapReplaceDictionary(config->value.objectCase->elsebranch, oldDict, newDict); + break; + case TSMAP_DICTIONARY: + if (config->value.objectDictionary == oldDict) + config->value.objectDictionary = newDict; + break; + } +} + +/* ---------------- + * Text Search Configuration Map Memory Management + * ---------------- + */ + +/* + * Move a FTS configuration expression to another memory context + */ +static TSMapElement * +TSMapExpressionMoveToMemoryContext(TSMapExpression *expression, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapExpression *resultExpression = MemoryContextAlloc(context, sizeof(TSMapExpression)); + + memset(resultExpression, 0, sizeof(TSMapExpression)); + result->value.objectExpression = resultExpression; + result->type = TSMAP_EXPRESSION; + + resultExpression->operator = expression->operator; + + resultExpression->left = TSMapMoveToMemoryContext(expression->left, context); + resultExpression->left->parent = result; + + resultExpression->right = TSMapMoveToMemoryContext(expression->right, context); + resultExpression->right->parent = result; + + return result; +} + +/* + * Move a FTS configuration case to another memory context + */ +static TSMapElement * +TSMapCaseMoveToMemoryContext(TSMapCase *caseObject, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapCase *resultCaseObject = MemoryContextAlloc(context, sizeof(TSMapCase)); + + memset(resultCaseObject, 0, sizeof(TSMapCase)); + result->value.objectCase = resultCaseObject; + result->type = TSMAP_CASE; + + resultCaseObject->match = caseObject->match; + + resultCaseObject->command = TSMapMoveToMemoryContext(caseObject->command, context); + resultCaseObject->command->parent = result; + + resultCaseObject->condition = TSMapMoveToMemoryContext(caseObject->condition, context); + resultCaseObject->condition->parent = result; + + if (caseObject->elsebranch != NULL) + { + resultCaseObject->elsebranch = TSMapMoveToMemoryContext(caseObject->elsebranch, context); + resultCaseObject->elsebranch->parent = result; + } + + return result; +} + +/* + * Move a FTS configuration to another memory context + */ +TSMapElement * +TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context) +{ + TSMapElement *result = NULL; + + switch (config->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionMoveToMemoryContext(config->value.objectExpression, context); + break; + case TSMAP_CASE: + result = TSMapCaseMoveToMemoryContext(config->value.objectCase, context); + break; + case TSMAP_DICTIONARY: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_DICTIONARY; + result->value.objectDictionary = config->value.objectDictionary; + break; + case TSMAP_KEEP: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_KEEP; + result->value.object = NULL; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + break; + } + + return result; +} + +/* + * Free memory occupied by FTS configuration expression + */ +static void +TSMapExpressionFree(TSMapExpression *expression) +{ + if (expression->left) + TSMapElementFree(expression->left); + if (expression->right) + TSMapElementFree(expression->right); + pfree(expression); +} + +/* + * Free memory occupied by FTS configuration case + */ +static void +TSMapCaseFree(TSMapCase *caseObject) +{ + TSMapElementFree(caseObject->condition); + TSMapElementFree(caseObject->command); + TSMapElementFree(caseObject->elsebranch); + pfree(caseObject); +} + +/* + * Free memory occupied by FTS configuration element + */ +void +TSMapElementFree(TSMapElement *element) +{ + if (element != NULL) + { + switch (element->type) + { + case TSMAP_CASE: + TSMapCaseFree(element->value.objectCase); + break; + case TSMAP_EXPRESSION: + TSMapExpressionFree(element->value.objectExpression); + break; + } + pfree(element); + } +} + +/* + * Do a deep comparison of two TSMapElements. Doesn't check parents of elements + */ +bool +TSMapElementEquals(TSMapElement *a, TSMapElement *b) +{ + bool result = true; + + if (a->type == b->type) + { + switch (a->type) + { + case TSMAP_CASE: + if (!TSMapElementEquals(a->value.objectCase->condition, b->value.objectCase->condition)) + result = false; + if (!TSMapElementEquals(a->value.objectCase->command, b->value.objectCase->command)) + result = false; + + if (a->value.objectCase->elsebranch != NULL && b->value.objectCase->elsebranch != NULL) + { + if (!TSMapElementEquals(a->value.objectCase->elsebranch, b->value.objectCase->elsebranch)) + result = false; + } + else if (a->value.objectCase->elsebranch != NULL || b->value.objectCase->elsebranch != NULL) + result = false; + + if (a->value.objectCase->match != b->value.objectCase->match) + result = false; + break; + case TSMAP_EXPRESSION: + if (!TSMapElementEquals(a->value.objectExpression->left, b->value.objectExpression->left)) + result = false; + if (!TSMapElementEquals(a->value.objectExpression->right, b->value.objectExpression->right)) + result = false; + if (a->value.objectExpression->operator != b->value.objectExpression->operator) + result = false; + break; + case TSMAP_DICTIONARY: + result = a->value.objectDictionary == b->value.objectDictionary; + break; + case TSMAP_KEEP: + result = true; + } + } + else + result = false; + + return result; +} diff --git a/src/backend/tsearch/ts_parse.c b/src/backend/tsearch/ts_parse.c index 7b69ef5660..f476abb323 100644 --- a/src/backend/tsearch/ts_parse.c +++ b/src/backend/tsearch/ts_parse.c @@ -16,58 +16,157 @@ #include "tsearch/ts_cache.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "funcapi.h" #define IGNORE_LONGLEXEME 1 -/* +/*------------------- * Lexize subsystem + *------------------- */ +/* + * Representation of token produced by FTS parser. It contains intermediate + * lexemes in case of phrase dictionary processing. + */ typedef struct ParsedLex { - int type; - char *lemm; - int lenlemm; - struct ParsedLex *next; + int type; /* Token type */ + char *lemm; /* Token itself */ + int lenlemm; /* Length of the token string */ + int maplen; /* Length of the map */ + bool *accepted; /* Is accepted by some dictionary */ + bool *rejected; /* Is rejected by all dictionaries */ + bool *notFinished; /* Some dictionary not finished processing and + * waits for more tokens */ + struct ParsedLex *next; /* Next token in the list */ + TSMapElement *relatedRule; /* Rule which is used to produce lexemes from + * the token */ } ParsedLex; +/* + * List of tokens produced by FTS parser. + */ typedef struct ListParsedLex { ParsedLex *head; ParsedLex *tail; } ListParsedLex; -typedef struct +/* + * Dictionary state shared between processing of different tokens + */ +typedef struct DictState { - TSConfigCacheEntry *cfg; - Oid curDictId; - int posDict; - DictSubState dictState; - ParsedLex *curSub; - ListParsedLex towork; /* current list to work */ - ListParsedLex waste; /* list of lexemes that already lexized */ + Oid relatedDictionary; /* DictState contains state of dictionary + * with this Oid */ + DictSubState subState; /* Internal state of the dictionary used to + * store some state between dictionary calls */ + ListParsedLex acceptedTokens; /* Tokens which are processed and + * accepted, used in last returned result + * by the dictionary */ + ListParsedLex intermediateTokens; /* Tokens which are not accepted, but + * were processed by thesaurus-like + * dictionary */ + bool storeToAccepted; /* Should current token be appended to + * accepted or intermediate tokens */ + bool processed; /* Is the dictionary take control during + * current token processing */ + TSLexeme *tmpResult; /* Last result returned by thesaurus-like + * dictionary, if dictionary still waiting for + * more lexemes */ +} DictState; - /* - * fields to store last variant to lexize (basically, thesaurus or similar - * to, which wants several lexemes - */ +/* + * List of dictionary states + */ +typedef struct DictStateList +{ + int listLength; + DictState *states; +} DictStateList; - ParsedLex *lastRes; - TSLexeme *tmpRes; +/* + * Buffer entry with lexemes produced from current token + */ +typedef struct LexemesBufferEntry +{ + TSMapElement *key; /* Element of the mapping configuration produced the entry */ + ParsedLex *token; /* Token used for production of the lexemes */ + TSLexeme *data; /* Lexemes produced from current token */ +} LexemesBufferEntry; + +/* + * Buffer with lexemes produced from current token + */ +typedef struct LexemesBuffer +{ + int size; + LexemesBufferEntry *data; +} LexemesBuffer; + +/* + * Storage for accepted and possible accepted lexemes + */ +typedef struct ResultStorage +{ + TSLexeme *lexemes; /* Processed lexemes, which is not yet + * accepted */ + TSLexeme *accepted; /* Already accepted lexemes */ +} ResultStorage; + +/* + * FTS processing context + */ +typedef struct LexizeData +{ + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + DictStateList dslist; /* List of all currently stored states of + * dictionaries */ + ListParsedLex towork; /* Current list to work */ + ListParsedLex waste; /* List of lexemes that already lexized */ + LexemesBuffer buffer; /* Buffer of processed lexemes. Used to avoid + * multiple execution of token lexize process + * with same parameters */ + ResultStorage delayedResults; /* Results that should be returned but may + * be rejected in future */ + Oid skipDictionary; /* The dictionary we should skip during + * processing. Used to avoid infinite loop in + * configuration with phrase dictionary */ + bool debugContext; /* If true, relatedRule attribute is filled */ } LexizeData; -static void -LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +/* + * FTS processing debug context. Used during ts_debug calls. + */ +typedef struct TSDebugContext { - ld->cfg = cfg; - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->towork.head = ld->towork.tail = ld->curSub = NULL; - ld->waste.head = ld->waste.tail = NULL; - ld->lastRes = NULL; - ld->tmpRes = NULL; -} + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + TSParserCacheEntry *prsobj; /* Parser context of current ts_debug context */ + LexDescr *tokenTypes; /* Token types supported by current parser */ + void *prsdata; /* Parser data of current ts_debug context */ + LexizeData ldata; /* Lexize data of current ts_debug context */ + int tokentype; /* Last token tokentype */ + TSLexeme *savedLexemes; /* Last token lexemes stored for ts_debug + * output */ + ParsedLex *leftTokens; /* Corresponded ParsedLex */ +} TSDebugContext; + +static TSLexeme *TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression); +static TSLexeme *LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config); + +/*------------------- + * ListParsedLex API + *------------------- + */ +/* + * Add a ParsedLex to the end of the list + */ static void LPLAddTail(ListParsedLex *list, ParsedLex *newpl) { @@ -81,274 +180,1291 @@ LPLAddTail(ListParsedLex *list, ParsedLex *newpl) newpl->next = NULL; } -static ParsedLex * -LPLRemoveHead(ListParsedLex *list) -{ - ParsedLex *res = list->head; +/* + * Add a copy of ParsedLex to the end of the list + */ +static void +LPLAddTailCopy(ListParsedLex *list, ParsedLex *newpl) +{ + ParsedLex *copy = palloc0(sizeof(ParsedLex)); + + copy->lenlemm = newpl->lenlemm; + copy->type = newpl->type; + copy->lemm = newpl->lemm; + copy->relatedRule = newpl->relatedRule; + copy->next = NULL; + + if (list->tail) + { + list->tail->next = copy; + list->tail = copy; + } + else + list->head = list->tail = copy; +} + +/* + * Remove the head of the list. Return pointer to detached head + */ +static ParsedLex * +LPLRemoveHead(ListParsedLex *list) +{ + ParsedLex *res = list->head; + + if (list->head) + list->head = list->head->next; + + if (list->head == NULL) + list->tail = NULL; + + return res; +} + +/* + * Remove all ParsedLex from the list + */ +static void +LPLClear(ListParsedLex *list) +{ + ParsedLex *tmp, + *ptr = list->head; + + while (ptr) + { + tmp = ptr->next; + pfree(ptr); + ptr = tmp; + } + + list->head = list->tail = NULL; +} + +/*------------------- + * LexizeData manipulation functions + *------------------- + */ + +/* + * Initialize empty LexizeData object + */ +static void +LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +{ + ld->cfg = cfg; + ld->skipDictionary = InvalidOid; + ld->towork.head = ld->towork.tail = NULL; + ld->waste.head = ld->waste.tail = NULL; + ld->dslist.listLength = 0; + ld->dslist.states = NULL; + ld->buffer.size = 0; + ld->buffer.data = NULL; + ld->delayedResults.lexemes = NULL; + ld->delayedResults.accepted = NULL; +} + +/* + * Add a token to the processing queue + */ +static void +LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) +{ + ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + + newpl->type = type; + newpl->lemm = lemm; + newpl->lenlemm = lenlemm; + newpl->relatedRule = NULL; + LPLAddTail(&ld->towork, newpl); +} + +/* + * Remove head of the processing queue + */ +static void +RemoveHead(LexizeData *ld) +{ + LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); +} + +/* + * Set token corresponded to current lexeme + */ +static void +setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +{ + if (correspondLexem) + *correspondLexem = ld->waste.head; + else + LPLClear(&ld->waste); + + ld->waste.head = ld->waste.tail = NULL; +} + +/*------------------- + * DictState manipulation functions + *------------------- + */ + +/* + * Get a state of dictionary based on its OID + */ +static DictState * +DictStateListGet(DictStateList *list, Oid dictId) +{ + int i; + DictState *result = NULL; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + result = &list->states[i]; + + return result; +} + +/* + * Remove a state of dictionary based on its OID + */ +static void +DictStateListRemove(DictStateList *list, Oid dictId) +{ + int i; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + break; + + if (i != list->listLength) + { + memcpy(list->states + i, list->states + i + 1, sizeof(DictState) * (list->listLength - i - 1)); + list->listLength--; + if (list->listLength == 0) + list->states = NULL; + else + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + } +} + +/* + * Insert a state of dictionary with specified OID + */ +static DictState * +DictStateListAdd(DictStateList *list, DictState *state) +{ + DictStateListRemove(list, state->relatedDictionary); + + list->listLength++; + if (list->states) + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + else + list->states = palloc0(sizeof(DictState) * list->listLength); + + memcpy(list->states + list->listLength - 1, state, sizeof(DictState)); + + return list->states + list->listLength - 1; +} + +/* + * Remove states of all dictionaries + */ +static void +DictStateListClear(DictStateList *list) +{ + list->listLength = 0; + if (list->states) + pfree(list->states); + list->states = NULL; +} + +/*------------------- + * LexemesBuffer manipulation functions + *------------------- + */ + +/* + * Check if there is a saved lexeme generated by specified TSMapElement + */ +static bool +LexemesBufferContains(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + return true; + + return false; +} + +/* + * Get a saved lexeme generated by specified TSMapElement + */ +static TSLexeme * +LexemesBufferGet(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + TSLexeme *result = NULL; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + result = buffer->data[i].data; + + return result; +} + +/* + * Remove a saved lexeme generated by specified TSMapElement + */ +static void +LexemesBufferRemove(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + break; + + if (i != buffer->size) + { + memcpy(buffer->data + i, buffer->data + i + 1, sizeof(LexemesBufferEntry) * (buffer->size - i - 1)); + buffer->size--; + if (buffer->size == 0) + buffer->data = NULL; + else + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + } +} + +/* + * Same a lexeme generated by specified TSMapElement + */ +static void +LexemesBufferAdd(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token, TSLexeme *data) +{ + LexemesBufferRemove(buffer, key, token); + + buffer->size++; + if (buffer->data) + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + else + buffer->data = palloc0(sizeof(LexemesBufferEntry) * buffer->size); + + buffer->data[buffer->size - 1].token = token; + buffer->data[buffer->size - 1].key = key; + buffer->data[buffer->size - 1].data = data; +} + +/* + * Remove all lexemes saved in a buffer + */ +static void +LexemesBufferClear(LexemesBuffer *buffer) +{ + int i; + bool *skipEntry = palloc0(sizeof(bool) * buffer->size); + + for (i = 0; i < buffer->size; i++) + { + if (buffer->data[i].data != NULL && !skipEntry[i]) + { + int j; + + for (j = 0; j < buffer->size; j++) + if (buffer->data[i].data == buffer->data[j].data) + skipEntry[j] = true; + + pfree(buffer->data[i].data); + } + } + + buffer->size = 0; + if (buffer->data) + pfree(buffer->data); + buffer->data = NULL; +} + +/*------------------- + * TSLexeme util functions + *------------------- + */ + +/* + * Get size of TSLexeme except empty-lexeme + */ +static int +TSLexemeGetSize(TSLexeme *lex) +{ + int result = 0; + TSLexeme *ptr = lex; + + while (ptr && ptr->lexeme) + { + result++; + ptr++; + } + + return result; +} + +/* + * Remove repeated lexemes. Also remove copies of whole nvariant groups. + */ +static TSLexeme * +TSLexemeRemoveDuplications(TSLexeme *lexeme) +{ + TSLexeme *res; + int curLexIndex; + int i; + int lexemeSize = TSLexemeGetSize(lexeme); + int shouldCopyCount = lexemeSize; + bool *shouldCopy; + + if (lexeme == NULL) + return NULL; + + shouldCopy = palloc(sizeof(bool) * lexemeSize); + memset(shouldCopy, true, sizeof(bool) * lexemeSize); + + for (curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + for (i = curLexIndex + 1; i < lexemeSize; i++) + { + if (!shouldCopy[i]) + continue; + + if (strcmp(lexeme[curLexIndex].lexeme, lexeme[i].lexeme) == 0) + { + if (lexeme[curLexIndex].nvariant == lexeme[i].nvariant) + { + shouldCopy[i] = false; + shouldCopyCount--; + continue; + } + else + { + /* + * Check for same set of lexemes in another nvariant + * series + */ + int nvariantCountL = 0; + int nvariantCountR = 0; + int nvariantOverlap = 1; + int j; + + for (j = 0; j < lexemeSize; j++) + if (lexeme[curLexIndex].nvariant == lexeme[j].nvariant) + nvariantCountL++; + for (j = 0; j < lexemeSize; j++) + if (lexeme[i].nvariant == lexeme[j].nvariant) + nvariantCountR++; + + if (nvariantCountL != nvariantCountR) + continue; + + for (j = 1; j < nvariantCountR; j++) + { + if (strcmp(lexeme[curLexIndex + j].lexeme, lexeme[i + j].lexeme) == 0 + && lexeme[curLexIndex + j].nvariant == lexeme[i + j].nvariant) + nvariantOverlap++; + } + + if (nvariantOverlap != nvariantCountR) + continue; + + for (j = 0; j < nvariantCountR; j++) + shouldCopy[i + j] = false; + } + } + } + } + + res = palloc0(sizeof(TSLexeme) * (shouldCopyCount + 1)); + + for (i = 0, curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + if (shouldCopy[curLexIndex]) + { + memcpy(res + i, lexeme + curLexIndex, sizeof(TSLexeme)); + i++; + } + } + + pfree(shouldCopy); + return res; +} + +/* + * Combine two lexeme lists with respect to positions + */ +static TSLexeme * +TSLexemeMergePositions(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + + if (left != NULL || right != NULL) + { + int left_i = 0; + int right_i = 0; + int left_max_nvariant = 0; + int i; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + for (i = 0; i < right_size; i++) + right[i].nvariant += left_max_nvariant; + if (right && right[0].flags & TSL_ADDPOS) + right[0].flags &= ~TSL_ADDPOS; + + i = 0; + while (i < left_size + right_size) + { + if (left_i < left_size) + { + do + { + result[i++] = left[left_i++]; + } while (left && left[left_i].lexeme && (left[left_i].flags & TSL_ADDPOS) == 0); + } + + if (right_i < right_size) + { + do + { + result[i++] = right[right_i++]; + } while (right && right[right_i].lexeme && (right[right_i].flags & TSL_ADDPOS) == 0); + } + } + } + return result; +} + +/* + * Split lexemes generated by regular dictionaries and multi-input dictionaries + * and combine them with respect to positions + */ +static TSLexeme * +TSLexemeFilterMulti(TSLexeme *lexemes) +{ + TSLexeme *result; + TSLexeme *ptr = lexemes; + int multi_lexemes = 0; + + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + multi_lexemes++; + ptr++; + } + + if (multi_lexemes > 0) + { + TSLexeme *lexemes_multi = palloc0(sizeof(TSLexeme) * (multi_lexemes + 1)); + TSLexeme *lexemes_rest = palloc0(sizeof(TSLexeme) * (TSLexemeGetSize(lexemes) - multi_lexemes + 1)); + int rest_i = 0; + int multi_i = 0; + + ptr = lexemes; + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + lexemes_multi[multi_i++] = *ptr; + else + lexemes_rest[rest_i++] = *ptr; + + ptr++; + } + result = TSLexemeMergePositions(lexemes_rest, lexemes_multi); + } + else + { + result = TSLexemeMergePositions(lexemes, NULL); + } + + return result; +} + +/* + * Mark lexemes as generated by multi-input (thesaurus-like) dictionary + */ +static void +TSLexemeMarkMulti(TSLexeme *lexemes) +{ + TSLexeme *ptr = lexemes; + + while (ptr && ptr->lexeme) + { + ptr->flags |= TSL_MULTI; + ptr++; + } +} + +/*------------------- + * Lexemes set operations + *------------------- + */ + +/* + * Combine left and right lexeme lists into one. + * If append is true, right lexemes added after last left lexeme with TSL_ADDPOS flag + */ +static TSLexeme * +TSLexemeUnionOpt(TSLexeme *left, TSLexeme *right, bool append) +{ + TSLexeme *result; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + int left_max_nvariant = 0; + int i; + + if (left == NULL && right == NULL) + { + result = NULL; + } + else + { + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + if (left_size > 0) + memcpy(result, left, sizeof(TSLexeme) * left_size); + if (right_size > 0) + memcpy(result + left_size, right, sizeof(TSLexeme) * right_size); + if (append && left_size > 0 && right_size > 0) + result[left_size].flags |= TSL_ADDPOS; + + for (i = left_size; i < left_size + right_size; i++) + result[i].nvariant += left_max_nvariant; + } + + return result; +} + +/* + * Combine left and right lexeme lists into one + */ +static TSLexeme * +TSLexemeUnion(TSLexeme *left, TSLexeme *right) +{ + return TSLexemeUnionOpt(left, right, false); +} + +/* + * Remove common lexemes and return only which is stored in left list + */ +static TSLexeme * +TSLexemeExcept(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (!found) + result[k++] = left[i]; + } + + return result; +} + +/* + * Keep only common lexemes + */ +static TSLexeme * +TSLexemeIntersect(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (found) + result[k++] = left[i]; + } + + return result; +} + +/*------------------- + * Result storage functions + *------------------- + */ + +/* + * Add a lexeme to the result storage + */ +static void +ResultStorageAdd(ResultStorage *storage, ParsedLex *token, TSLexeme *lexs) +{ + TSLexeme *oldLexs = storage->lexemes; + + storage->lexemes = TSLexemeUnionOpt(storage->lexemes, lexs, true); + if (oldLexs) + pfree(oldLexs); +} + +/* + * Move all saved lexemes to accepted list + */ +static void +ResultStorageMoveToAccepted(ResultStorage *storage) +{ + if (storage->accepted) + { + TSLexeme *prevAccepted = storage->accepted; + + storage->accepted = TSLexemeUnionOpt(storage->accepted, storage->lexemes, true); + if (prevAccepted) + pfree(prevAccepted); + if (storage->lexemes) + pfree(storage->lexemes); + } + else + { + storage->accepted = storage->lexemes; + } + storage->lexemes = NULL; +} + +/* + * Remove all non-accepted lexemes + */ +static void +ResultStorageClearLexemes(ResultStorage *storage) +{ + if (storage->lexemes) + pfree(storage->lexemes); + storage->lexemes = NULL; +} + +/* + * Remove all accepted lexemes + */ +static void +ResultStorageClearAccepted(ResultStorage *storage) +{ + if (storage->accepted) + pfree(storage->accepted); + storage->accepted = NULL; +} + +/*------------------- + * Condition and command execution + *------------------- + */ + +/* + * Process a token by the dictionary + */ +static TSLexeme * +LexizeExecDictionary(LexizeData *ld, ParsedLex *token, TSMapElement *dictionary) +{ + TSLexeme *res; + TSDictionaryCacheEntry *dict; + DictSubState subState; + Oid dictId = dictionary->value.objectDictionary; + + if (ld->skipDictionary == dictId) + return NULL; + + if (LexemesBufferContains(&ld->buffer, dictionary, token)) + res = LexemesBufferGet(&ld->buffer, dictionary, token); + else + { + char *curValLemm = token->lemm; + int curValLenLemm = token->lenlemm; + DictState *state = DictStateListGet(&ld->dslist, dictId); + + dict = lookup_ts_dictionary_cache(dictId); + + if (state) + { + subState = state->subState; + state->processed = true; + } + else + { + subState.isend = subState.getnext = false; + subState.private_state = NULL; + } + + res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(dict->lexize), + PointerGetDatum(dict->dictData), + PointerGetDatum(curValLemm), + Int32GetDatum(curValLenLemm), + PointerGetDatum(&subState) + )); + + if (subState.getnext) + { + /* + * Dictionary wants next word, so store current context and state + * in the DictStateList + */ + if (state == NULL) + { + state = palloc0(sizeof(DictState)); + state->processed = true; + state->relatedDictionary = dictId; + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + state->acceptedTokens.head = state->acceptedTokens.tail = NULL; + state->tmpResult = NULL; + + /* + * Add state to the list and update pointer in order to work + * with copy from the list + */ + state = DictStateListAdd(&ld->dslist, state); + } + + state->subState = subState; + state->storeToAccepted = res != NULL; + + if (res) + { + if (state->intermediateTokens.head != NULL) + { + ParsedLex *ptr = state->intermediateTokens.head; + + while (ptr) + { + LPLAddTailCopy(&state->acceptedTokens, ptr); + ptr = ptr->next; + } + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + } + + if (state->tmpResult) + pfree(state->tmpResult); + TSLexemeMarkMulti(res); + state->tmpResult = res; + res = NULL; + } + } + else if (state != NULL) + { + if (res) + { + if (state) + TSLexemeMarkMulti(res); + DictStateListRemove(&ld->dslist, dictId); + } + else + { + /* + * Trigger post-processing in order to check tmpResult and + * restart processing (see LexizeExec function) + */ + state->processed = false; + } + } + LexemesBufferAdd(&ld->buffer, dictionary, token, res); + } + + return res; +} + +/* + * Check is dictionary waits for more tokens or not + */ +static bool +LexizeExecDictionaryWaitNext(LexizeData *ld, Oid dictId) +{ + DictState *state = DictStateListGet(&ld->dslist, dictId); + + if (state) + return state->subState.getnext; + else + return false; +} + +/* + * Check is dictionary result for current token is NULL or not. + * It dictionary waits for more lexemes, the result is interpreted as not null. + */ +static bool +LexizeExecIsNull(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + bool result = false; + + if (config->type == TSMAP_EXPRESSION) + { + TSMapExpression *expression = config->value.objectExpression; + + result = LexizeExecIsNull(ld, token, expression->left) || LexizeExecIsNull(ld, token, expression->right); + } + else if (config->type == TSMAP_DICTIONARY) + { + Oid dictOid = config->value.objectDictionary; + TSLexeme *lexemes = LexizeExecDictionary(ld, token, config); + + if (lexemes) + result = false; + else + result = !LexizeExecDictionaryWaitNext(ld, dictOid); + } + return result; +} + +/* + * Execute a MAP operator + */ +static TSLexeme * +TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression) +{ + TSLexeme *left_res; + TSLexeme *result = NULL; + int left_size; + int i; + + left_res = LexizeExecTSElement(ld, token, expression->left); + left_size = TSLexemeGetSize(left_res); + + if (left_res == NULL && LexizeExecIsNull(ld, token, expression->left)) + result = LexizeExecTSElement(ld, token, expression->right); + else if (expression->operator == TSMAP_OP_COMMA && + ((left_res != NULL && (left_res->flags & TSL_FILTER) == 0) || left_res == NULL)) + result = left_res; + else + { + TSMapElement *relatedRuleTmp = NULL; + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + relatedRuleTmp->value.objectExpression->left = token->relatedRule; + + for (i = 0; i < left_size; i++) + { + TSLexeme *tmp_res = NULL; + TSLexeme *prev_res; + ParsedLex tmp_token; + + tmp_token.lemm = left_res[i].lexeme; + tmp_token.lenlemm = strlen(left_res[i].lexeme); + tmp_token.type = token->type; + tmp_token.next = NULL; + + tmp_res = LexizeExecTSElement(ld, &tmp_token, expression->right); + relatedRuleTmp->value.objectExpression->right = tmp_token.relatedRule; + prev_res = result; + result = TSLexemeUnion(prev_res, tmp_res); + if (prev_res) + pfree(prev_res); + } + token->relatedRule = relatedRuleTmp; + } + + return result; +} + +/* + * Execute a TSMapElement + * Common point of all possible types of TSMapElement + */ +static TSLexeme * +LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + TSLexeme *result = NULL; + + if (LexemesBufferContains(&ld->buffer, config, token)) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexemesBufferGet(&ld->buffer, config, token); + } + else if (config->type == TSMAP_DICTIONARY) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexizeExecDictionary(ld, token, config); + } + else if (config->type == TSMAP_CASE) + { + TSMapCase *caseObject = config->value.objectCase; + bool conditionIsNull = LexizeExecIsNull(ld, token, caseObject->condition); + + if ((!conditionIsNull && caseObject->match) || (conditionIsNull && !caseObject->match)) + { + if (caseObject->command->type == TSMAP_KEEP) + result = LexizeExecTSElement(ld, token, caseObject->condition); + else + result = LexizeExecTSElement(ld, token, caseObject->command); + } + else if (caseObject->elsebranch) + result = LexizeExecTSElement(ld, token, caseObject->elsebranch); + } + else if (config->type == TSMAP_EXPRESSION) + { + TSLexeme *resLeft = NULL; + TSLexeme *resRight = NULL; + TSMapElement *relatedRuleTmp = NULL; + TSMapExpression *expression = config->value.objectExpression; + + if (expression->operator != TSMAP_OP_MAP && expression->operator != TSMAP_OP_COMMA) + { + if (ld->debugContext) + { + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + } - if (list->head) - list->head = list->head->next; + resLeft = LexizeExecTSElement(ld, token, expression->left); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->left = token->relatedRule; - if (list->head == NULL) - list->tail = NULL; + resRight = LexizeExecTSElement(ld, token, expression->right); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->right = token->relatedRule; + } - return res; -} + switch (expression->operator) + { + case TSMAP_OP_UNION: + result = TSLexemeUnion(resLeft, resRight); + break; + case TSMAP_OP_EXCEPT: + result = TSLexemeExcept(resLeft, resRight); + break; + case TSMAP_OP_INTERSECT: + result = TSLexemeIntersect(resLeft, resRight); + break; + case TSMAP_OP_MAP: + case TSMAP_OP_COMMA: + result = TSLexemeMap(ld, token, expression); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } -static void -LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) -{ - ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + if (ld->debugContext && relatedRuleTmp != NULL) + token->relatedRule = relatedRuleTmp; + } - newpl->type = type; - newpl->lemm = lemm; - newpl->lenlemm = lenlemm; - LPLAddTail(&ld->towork, newpl); - ld->curSub = ld->towork.tail; + if (!LexemesBufferContains(&ld->buffer, config, token)) + LexemesBufferAdd(&ld->buffer, config, token, result); + + return result; } -static void -RemoveHead(LexizeData *ld) +/*------------------- + * LexizeExec and helpers functions + *------------------- + */ + +/* + * Processing of EOF-like token. + * Return all temporary results if any are saved. + */ +static TSLexeme * +LexizeExecFinishProcessing(LexizeData *ld) { - LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); + int i; + TSLexeme *res = NULL; + + for (i = 0; i < ld->dslist.listLength; i++) + { + TSLexeme *last_res = res; - ld->posDict = 0; + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); + } + + return res; } -static void -setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +/* + * Get last accepted result of the phrase-dictionary + */ +static TSLexeme * +LexizeExecGetPreviousResults(LexizeData *ld) { - if (correspondLexem) - { - *correspondLexem = ld->waste.head; - } - else - { - ParsedLex *tmp, - *ptr = ld->waste.head; + int i; + TSLexeme *res = NULL; - while (ptr) + for (i = 0; i < ld->dslist.listLength; i++) + { + if (!ld->dslist.states[i].processed) { - tmp = ptr->next; - pfree(ptr); - ptr = tmp; + TSLexeme *last_res = res; + + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); } } - ld->waste.head = ld->waste.tail = NULL; + + return res; } +/* + * Remove all dictionary states which wasn't used for current token + */ static void -moveToWaste(LexizeData *ld, ParsedLex *stop) +LexizeExecClearDictStates(LexizeData *ld) { - bool go = true; + int i; - while (ld->towork.head && go) + for (i = 0; i < ld->dslist.listLength; i++) { - if (ld->towork.head == stop) + if (!ld->dslist.states[i].processed) { - ld->curSub = stop->next; - go = false; + DictStateListRemove(&ld->dslist, ld->dslist.states[i].relatedDictionary); + i = 0; } - RemoveHead(ld); } } -static void -setNewTmpRes(LexizeData *ld, ParsedLex *lex, TSLexeme *res) +/* + * Check if there are any dictionaries that didn't processed current token + */ +static bool +LexizeExecNotProcessedDictStates(LexizeData *ld) { - if (ld->tmpRes) - { - TSLexeme *ptr; + int i; - for (ptr = ld->tmpRes; ptr->lexeme; ptr++) - pfree(ptr->lexeme); - pfree(ld->tmpRes); - } - ld->tmpRes = res; - ld->lastRes = lex; + for (i = 0; i < ld->dslist.listLength; i++) + if (!ld->dslist.states[i].processed) + return true; + + return false; } +/* + * Do a lexize processing for a towork queue in LexizeData + */ static TSLexeme * LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) { + ParsedLex *token; + TSMapElement *config; + TSLexeme *res = NULL; + TSLexeme *prevIterationResult = NULL; + bool removeHead = false; + bool resetSkipDictionary = false; + bool accepted = false; int i; - ListDictionary *map; - TSDictionaryCacheEntry *dict; - TSLexeme *res; - if (ld->curDictId == InvalidOid) + for (i = 0; i < ld->dslist.listLength; i++) + ld->dslist.states[i].processed = false; + if (ld->skipDictionary != InvalidOid) + resetSkipDictionary = true; + + token = ld->towork.head; + if (token == NULL) { - /* - * usual mode: dictionary wants only one word, but we should keep in - * mind that we should go through all stack - */ + setCorrLex(ld, correspondLexem); + return NULL; + } - while (ld->towork.head) + if (token->type >= ld->cfg->lenmap) + { + removeHead = true; + } + else + { + config = ld->cfg->map[token->type]; + if (config != NULL) + { + res = LexizeExecTSElement(ld, token, config); + prevIterationResult = LexizeExecGetPreviousResults(ld); + removeHead = prevIterationResult == NULL; + } + else { - ParsedLex *curVal = ld->towork.head; - char *curValLemm = curVal->lemm; - int curValLenLemm = curVal->lenlemm; + removeHead = true; + if (token->type == 0) /* Processing EOF-like token */ + { + res = LexizeExecFinishProcessing(ld); + prevIterationResult = NULL; + } + } - map = ld->cfg->map + curVal->type; + if (LexizeExecNotProcessedDictStates(ld) && (token->type == 0 || config != NULL)) /* Rollback processing */ + { + int i; + ListParsedLex *intermediateTokens = NULL; + ListParsedLex *acceptedTokens = NULL; - if (curVal->type == 0 || curVal->type >= ld->cfg->lenmap || map->len == 0) + for (i = 0; i < ld->dslist.listLength; i++) { - /* skip this type of lexeme */ - RemoveHead(ld); - continue; + if (!ld->dslist.states[i].processed) + { + intermediateTokens = &ld->dslist.states[i].intermediateTokens; + acceptedTokens = &ld->dslist.states[i].acceptedTokens; + if (prevIterationResult == NULL) + ld->skipDictionary = ld->dslist.states[i].relatedDictionary; + } } - for (i = ld->posDict; i < map->len; i++) + if (intermediateTokens && intermediateTokens->head) { - dict = lookup_ts_dictionary_cache(map->dictIds[i]); - - ld->dictState.isend = ld->dictState.getnext = false; - ld->dictState.private_state = NULL; - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curValLemm), - Int32GetDatum(curValLenLemm), - PointerGetDatum(&ld->dictState) - )); - - if (ld->dictState.getnext) + ParsedLex *head = ld->towork.head; + + ld->towork.head = intermediateTokens->head; + intermediateTokens->tail->next = head; + head->next = NULL; + ld->towork.tail = head; + removeHead = false; + LPLClear(&ld->waste); + if (acceptedTokens && acceptedTokens->head) { - /* - * dictionary wants next word, so setup and store current - * position and go to multiword mode - */ - - ld->curDictId = DatumGetObjectId(map->dictIds[i]); - ld->posDict = i + 1; - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - return LexizeExec(ld, correspondLexem); + ld->waste.head = acceptedTokens->head; + ld->waste.tail = acceptedTokens->tail; } + } + ResultStorageClearLexemes(&ld->delayedResults); + if (config != NULL) + res = NULL; + } - if (!res) /* dictionary doesn't know this lexeme */ - continue; + if (config != NULL) + LexizeExecClearDictStates(ld); + else if (token->type == 0) + DictStateListClear(&ld->dslist); + } - if (res->flags & TSL_FILTER) - { - curValLemm = res->lexeme; - curValLenLemm = strlen(res->lexeme); - continue; - } + if (prevIterationResult) + res = prevIterationResult; + else + { + int i; - RemoveHead(ld); - setCorrLex(ld, correspondLexem); - return res; + for (i = 0; i < ld->dslist.listLength; i++) + { + if (ld->dslist.states[i].storeToAccepted) + { + LPLAddTailCopy(&ld->dslist.states[i].acceptedTokens, token); + accepted = true; + ld->dslist.states[i].storeToAccepted = false; + } + else + { + LPLAddTailCopy(&ld->dslist.states[i].intermediateTokens, token); } - - RemoveHead(ld); } } - else - { /* curDictId is valid */ - dict = lookup_ts_dictionary_cache(ld->curDictId); + if (removeHead) + RemoveHead(ld); + + if (ld->dslist.listLength > 0) + { /* - * Dictionary ld->curDictId asks us about following words + * There is at least one thesaurus dictionary in the middle of + * processing. Delay return of the result to avoid wrong lexemes in + * case of thesaurus phrase rejection. */ + ResultStorageAdd(&ld->delayedResults, token, res); + if (accepted) + ResultStorageMoveToAccepted(&ld->delayedResults); - while (ld->curSub) + /* + * Current value of res should not be cleared, because it is stored in + * LexemesBuffer + */ + res = NULL; + } + else + { + if (ld->towork.head == NULL) { - ParsedLex *curVal = ld->curSub; - - map = ld->cfg->map + curVal->type; - - if (curVal->type != 0) - { - bool dictExists = false; - - if (curVal->type >= ld->cfg->lenmap || map->len == 0) - { - /* skip this type of lexeme */ - ld->curSub = curVal->next; - continue; - } + TSLexeme *oldAccepted = ld->delayedResults.accepted; - /* - * We should be sure that current type of lexeme is recognized - * by our dictionary: we just check is it exist in list of - * dictionaries ? - */ - for (i = 0; i < map->len && !dictExists; i++) - if (ld->curDictId == DatumGetObjectId(map->dictIds[i])) - dictExists = true; - - if (!dictExists) - { - /* - * Dictionary can't work with current tpe of lexeme, - * return to basic mode and redo all stored lexemes - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } - } + ld->delayedResults.accepted = TSLexemeUnionOpt(ld->delayedResults.accepted, ld->delayedResults.lexemes, true); + if (oldAccepted) + pfree(oldAccepted); + } - ld->dictState.isend = (curVal->type == 0) ? true : false; - ld->dictState.getnext = false; + /* + * Add accepted delayed results to the output of the parsing. All + * lexemes returned during thesaurus phrase processing should be + * returned simultaneously, since all phrase tokens are processed as + * one. + */ + if (ld->delayedResults.accepted != NULL) + { + /* + * Previous value of res should not be cleared, because it is + * stored in LexemesBuffer + */ + res = TSLexemeUnionOpt(ld->delayedResults.accepted, res, prevIterationResult == NULL); - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curVal->lemm), - Int32GetDatum(curVal->lenlemm), - PointerGetDatum(&ld->dictState) - )); + ResultStorageClearLexemes(&ld->delayedResults); + ResultStorageClearAccepted(&ld->delayedResults); + } + setCorrLex(ld, correspondLexem); + } - if (ld->dictState.getnext) - { - /* Dictionary wants one more */ - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - continue; - } + if (resetSkipDictionary) + ld->skipDictionary = InvalidOid; - if (res || ld->tmpRes) - { - /* - * Dictionary normalizes lexemes, so we remove from stack all - * used lexemes, return to basic mode and redo end of stack - * (if it exists) - */ - if (res) - { - moveToWaste(ld, ld->curSub); - } - else - { - res = ld->tmpRes; - moveToWaste(ld, ld->lastRes); - } + res = TSLexemeFilterMulti(res); + if (res) + res = TSLexemeRemoveDuplications(res); - /* reset to initial state */ - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->lastRes = NULL; - ld->tmpRes = NULL; - setCorrLex(ld, correspondLexem); - return res; - } + /* + * Copy result since it may be stored in LexemesBuffere and removed at the + * next step. + */ + if (res) + { + TSLexeme *oldRes = res; + int resSize = TSLexemeGetSize(res); - /* - * Dict don't want next lexem and didn't recognize anything, redo - * from ld->towork.head - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } + res = palloc0(sizeof(TSLexeme) * (resSize + 1)); + memcpy(res, oldRes, sizeof(TSLexeme) * resSize); } - setCorrLex(ld, correspondLexem); - return NULL; + LexemesBufferClear(&ld->buffer); + return res; } +/*------------------- + * ts_parse API functions + *------------------- + */ + /* * Parse string and lexize words. * @@ -357,7 +1473,7 @@ LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) void parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; @@ -375,36 +1491,42 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } while ((norms = LexizeExec(&ldata, NULL)) != NULL) { - TSLexeme *ptr = norms; + TSLexeme *ptr; + + ptr = norms; prs->pos++; /* set pos */ @@ -429,14 +1551,246 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) } pfree(norms); } - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } +/*------------------- + * ts_debug and helper functions + *------------------- + */ + +/* + * Free memory occupied by temporary TSMapElement + */ + +static void +ts_debug_free_rule(TSMapElement *element) +{ + if (element != NULL && element->type == TSMAP_EXPRESSION) + { + ts_debug_free_rule(element->value.objectExpression->left); + ts_debug_free_rule(element->value.objectExpression->right); + pfree(element->value.objectExpression); + pfree(element); + } +} + +/* + * Initialize SRF context and text parser for ts_debug execution. + */ +static void +ts_debug_init(Oid cfgId, text *inputText, FunctionCallInfo fcinfo) +{ + TupleDesc tupdesc; + char *buf; + int buflen; + FuncCallContext *funcctx; + MemoryContext oldcontext; + TSDebugContext *context; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + buf = text_to_cstring(inputText); + buflen = strlen(buf); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + funcctx->user_fctx = palloc0(sizeof(TSDebugContext)); + funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + context = funcctx->user_fctx; + context->cfg = lookup_ts_config_cache(cfgId); + context->prsobj = lookup_ts_parser_cache(context->cfg->prsId); + + context->tokenTypes = (LexDescr *) DatumGetPointer(OidFunctionCall1(context->prsobj->lextypeOid, + (Datum) 0)); + + context->prsdata = (void *) DatumGetPointer(FunctionCall2(&context->prsobj->prsstart, + PointerGetDatum(buf), + Int32GetDatum(buflen))); + LexizeInit(&context->ldata, context->cfg); + context->ldata.debugContext = true; + context->tokentype = 1; + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Get one token from input text and add it to processing queue. + */ +static void +ts_debug_get_token(FuncCallContext *funcctx) +{ + TSDebugContext *context; + MemoryContext oldcontext; + int lenlemm; + char *lemm = NULL; + + context = funcctx->user_fctx; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + context->tokentype = DatumGetInt32(FunctionCall3(&(context->prsobj->prstoken), + PointerGetDatum(context->prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (context->tokentype > 0 && lenlemm >= MAXSTRLEN) + { +#ifdef IGNORE_LONGLEXEME + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#else + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#endif + } + + LexizeAddLemm(&context->ldata, context->tokentype, lemm, lenlemm); + MemoryContextSwitchTo(oldcontext); +} + /* + * Parse text and print debug information, such as token type, dictionary map + * configuration, selected command and lexemes for each token. + * Arguments: regconfiguration(Oid) cfgId, text *inputText + */ +Datum +ts_debug(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TSDebugContext *context; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) + { + Oid cfgId = PG_GETARG_OID(0); + text *inputText = PG_GETARG_TEXT_P(1); + + ts_debug_init(cfgId, inputText, fcinfo); + } + + funcctx = SRF_PERCALL_SETUP(); + context = funcctx->user_fctx; + + while (context->tokentype > 0 && context->leftTokens == NULL) + { + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + ts_debug_get_token(funcctx); + + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + MemoryContextSwitchTo(oldcontext); + } + + while (context->leftTokens == NULL && context->ldata.towork.head != NULL) + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + if (context->leftTokens && context->leftTokens && context->leftTokens->type > 0) + { + HeapTuple tuple; + Datum result; + char **values; + ParsedLex *lex = context->leftTokens; + StringInfo str = NULL; + TSLexeme *ptr; + + values = palloc0(sizeof(char *) * 7); + str = makeStringInfo(); + initStringInfo(str); + + values[0] = context->tokenTypes[lex->type - 1].alias; + values[1] = context->tokenTypes[lex->type - 1].descr; + + values[2] = palloc0(sizeof(char) * (lex->lenlemm + 1)); + memcpy(values[2], lex->lemm, sizeof(char) * lex->lenlemm); + + initStringInfo(str); + appendStringInfoChar(str, '{'); + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + Oid *dictionaries = TSMapGetDictionaries(context->ldata.cfg->map[lex->type]); + Oid *currentDictionary = NULL; + for (currentDictionary = dictionaries; *currentDictionary != InvalidOid; currentDictionary++) + { + if (currentDictionary != dictionaries) + appendStringInfoChar(str, ','); + + TSMapPrintDictName(*currentDictionary, str); + } + } + appendStringInfoChar(str, '}'); + values[3] = str->data; + + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + initStringInfo(str); + TSMapPrintElement(context->ldata.cfg->map[lex->type], str); + values[4] = str->data; + + initStringInfo(str); + if (lex->relatedRule) + { + TSMapPrintElement(lex->relatedRule, str); + values[5] = str->data; + str = makeStringInfo(); + initStringInfo(str); + ts_debug_free_rule(lex->relatedRule); + lex->relatedRule = NULL; + } + } + + initStringInfo(str); + ptr = context->savedLexemes; + if (context->savedLexemes) + appendStringInfoChar(str, '{'); + + while (ptr && ptr->lexeme) + { + if (ptr != context->savedLexemes) + appendStringInfoString(str, ", "); + appendStringInfoString(str, ptr->lexeme); + ptr++; + } + if (context->savedLexemes) + appendStringInfoChar(str, '}'); + if (context->savedLexemes) + values[6] = str->data; + else + values[6] = NULL; + + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + result = HeapTupleGetDatum(tuple); + + context->leftTokens = lex->next; + pfree(lex); + if (context->leftTokens == NULL && context->savedLexemes) + pfree(context->savedLexemes); + + SRF_RETURN_NEXT(funcctx, result); + } + + FunctionCall1(&(context->prsobj->prsend), PointerGetDatum(context->prsdata)); + SRF_RETURN_DONE(funcctx); +} + +/*------------------- * Headline framework + *------------------- */ + static void hladdword(HeadlineParsedText *prs, char *buf, int buflen, int type) { @@ -532,12 +1886,12 @@ addHLParsedLex(HeadlineParsedText *prs, TSQuery query, ParsedLex *lexs, TSLexeme void hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; TSLexeme *norms; - ParsedLex *lexs; + ParsedLex *lexs = NULL; TSConfigCacheEntry *cfg; TSParserCacheEntry *prsobj; void *prsdata; @@ -551,32 +1905,36 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } do { @@ -587,9 +1945,10 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu } else addHLParsedLex(prs, query, lexs, NULL); + lexs = NULL; } while (norms); - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } @@ -642,14 +2001,14 @@ generateHeadline(HeadlineParsedText *prs) } else if (!wrd->skip) { - if (wrd->selected) + if (wrd->selected && (wrd == prs->words || !(wrd - 1)->selected)) { memcpy(ptr, prs->startsel, prs->startsellen); ptr += prs->startsellen; } memcpy(ptr, wrd->word, wrd->len); ptr += wrd->len; - if (wrd->selected) + if (wrd->selected && ((wrd + 1 - prs->words) == prs->curwords || !(wrd + 1)->selected)) { memcpy(ptr, prs->stopsel, prs->stopsellen); ptr += prs->stopsellen; diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c index f6e03aea4f..0dd846bece 100644 --- a/src/backend/tsearch/ts_utils.c +++ b/src/backend/tsearch/ts_utils.c @@ -20,7 +20,6 @@ #include "tsearch/ts_locale.h" #include "tsearch/ts_utils.h" - /* * Given the base name and extension of a tsearch config file, return * its full path name. The base name is assumed to be user-supplied, diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 2b381782a3..f251e83ff6 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -828,11 +828,10 @@ static const struct cachedesc cacheinfo[] = { }, {TSConfigMapRelationId, /* TSCONFIGMAP */ TSConfigMapIndexId, - 3, + 2, { Anum_pg_ts_config_map_mapcfg, Anum_pg_ts_config_map_maptokentype, - Anum_pg_ts_config_map_mapseqno, 0 }, 2 diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index 97347780d3..1ff1a9255c 100644 --- a/src/backend/utils/cache/ts_cache.c +++ b/src/backend/utils/cache/ts_cache.c @@ -39,6 +39,7 @@ #include "catalog/pg_ts_template.h" #include "commands/defrem.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" @@ -51,13 +52,12 @@ /* - * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size + * MAXTOKENTYPE is arbitrary limits on the workspace size * used in lookup_ts_config_cache(). We could avoid hardwiring a limit * by making the workspace dynamically enlargeable, but it seems unlikely * to be worth the trouble. */ -#define MAXTOKENTYPE 256 -#define MAXDICTSPERTT 100 +#define MAXTOKENTYPE 256 static HTAB *TSParserCacheHash = NULL; @@ -418,11 +418,10 @@ lookup_ts_config_cache(Oid cfgId) ScanKeyData mapskey; SysScanDesc mapscan; HeapTuple maptup; - ListDictionary maplists[MAXTOKENTYPE + 1]; - Oid mapdicts[MAXDICTSPERTT]; + TSMapElement *mapconfigs[MAXTOKENTYPE + 1]; int maxtokentype; - int ndicts; int i; + TSMapElement *tmpConfig; tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); if (!HeapTupleIsValid(tp)) @@ -453,8 +452,8 @@ lookup_ts_config_cache(Oid cfgId) if (entry->map) { for (i = 0; i < entry->lenmap; i++) - if (entry->map[i].dictIds) - pfree(entry->map[i].dictIds); + if (entry->map[i]) + TSMapElementFree(entry->map[i]); pfree(entry->map); } } @@ -468,13 +467,11 @@ lookup_ts_config_cache(Oid cfgId) /* * Scan pg_ts_config_map to gather dictionary list for each token type * - * Because the index is on (mapcfg, maptokentype, mapseqno), we will - * see the entries in maptokentype order, and in mapseqno order for - * each token type, even though we didn't explicitly ask for that. + * Because the index is on (mapcfg, maptokentype), we will see the + * entries in maptokentype order even though we didn't explicitly ask + * for that. */ - MemSet(maplists, 0, sizeof(maplists)); maxtokentype = 0; - ndicts = 0; ScanKeyInit(&mapskey, Anum_pg_ts_config_map_mapcfg, @@ -486,6 +483,7 @@ lookup_ts_config_cache(Oid cfgId) mapscan = systable_beginscan_ordered(maprel, mapidx, NULL, 1, &mapskey); + memset(mapconfigs, 0, sizeof(mapconfigs)); while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); @@ -495,51 +493,27 @@ lookup_ts_config_cache(Oid cfgId) elog(ERROR, "maptokentype value %d is out of range", toktype); if (toktype < maxtokentype) elog(ERROR, "maptokentype entries are out of order"); - if (toktype > maxtokentype) - { - /* starting a new token type, but first save the prior data */ - if (ndicts > 0) - { - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - } - maxtokentype = toktype; - mapdicts[0] = cfgmap->mapdict; - ndicts = 1; - } - else - { - /* continuing data for current token type */ - if (ndicts >= MAXDICTSPERTT) - elog(ERROR, "too many pg_ts_config_map entries for one token type"); - mapdicts[ndicts++] = cfgmap->mapdict; - } + + maxtokentype = toktype; + tmpConfig = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + mapconfigs[maxtokentype] = TSMapMoveToMemoryContext(tmpConfig, CacheMemoryContext); + TSMapElementFree(tmpConfig); + tmpConfig = NULL; } systable_endscan_ordered(mapscan); index_close(mapidx, AccessShareLock); heap_close(maprel, AccessShareLock); - if (ndicts > 0) + if (maxtokentype > 0) { - /* save the last token type's dictionaries */ - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - /* and save the overall map */ + /* save the overall map */ entry->lenmap = maxtokentype + 1; - entry->map = (ListDictionary *) + entry->map = (TSMapElement * *) MemoryContextAlloc(CacheMemoryContext, - sizeof(ListDictionary) * entry->lenmap); - memcpy(entry->map, maplists, - sizeof(ListDictionary) * entry->lenmap); + sizeof(TSMapElement *) * entry->lenmap); + memcpy(entry->map, mapconfigs, + sizeof(TSMapElement *) * entry->lenmap); } entry->isvalid = true; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d066f4f00b..c5cb3c62f7 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -14223,15 +14223,29 @@ dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo) PQclear(res); resetPQExpBuffer(query); - appendPQExpBuffer(query, - "SELECT\n" - " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" - " m.mapdict::pg_catalog.regdictionary AS dictname\n" - "FROM pg_catalog.pg_ts_config_map AS m\n" - "WHERE m.mapcfg = '%u'\n" - "ORDER BY m.mapcfg, m.maptokentype, m.mapseqno", - cfginfo->cfgparser, cfginfo->dobj.catId.oid); + + if (fout->remoteVersion >= 110000) + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); + else + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " m.mapdict::pg_catalog.regdictionary AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype, m.mapseqno\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 0c3be1f504..729242e8e0 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -4646,25 +4646,41 @@ describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname, initPQExpBuffer(&buf); - printfPQExpBuffer(&buf, - "SELECT\n" - " ( SELECT t.alias FROM\n" - " pg_catalog.ts_token_type(c.cfgparser) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" - " pg_catalog.btrim(\n" - " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" - " FROM pg_catalog.pg_ts_config_map AS mm\n" - " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" - " ORDER BY mapcfg, maptokentype, mapseqno\n" - " ) :: pg_catalog.text,\n" - " '{}') AS \"%s\"\n" - "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" - "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" - "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" - "ORDER BY 1;", - gettext_noop("Token"), - gettext_noop("Dictionaries"), - oid); + if (pset.sversion >= 110000) + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + else + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " pg_catalog.btrim(\n" + " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" + " FROM pg_catalog.pg_ts_config_map AS mm\n" + " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" + " ORDER BY mapcfg, maptokentype, mapseqno\n" + " ) :: pg_catalog.text,\n" + " '{}') AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + res = PSQLexec(buf.data); termPQExpBuffer(&buf); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 7dd9d108d6..589bce476b 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -262,7 +262,7 @@ DECLARE_UNIQUE_INDEX(pg_ts_config_cfgname_index, 3608, on pg_ts_config using btr DECLARE_UNIQUE_INDEX(pg_ts_config_oid_index, 3712, on pg_ts_config using btree(oid oid_ops)); #define TSConfigOidIndexId 3712 -DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops, mapseqno int4_ops)); +DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops)); #define TSConfigMapIndexId 3609 DECLARE_UNIQUE_INDEX(pg_ts_dict_dictname_index, 3604, on pg_ts_dict using btree(dictname name_ops, dictnamespace oid_ops)); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 9bf20c059b..bd9549ac39 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4988,6 +4988,12 @@ DESCR("transform jsonb to tsvector"); DATA(insert OID = 4212 ( to_tsvector PGNSP PGUID 12 100 0 0 0 f f f t f i s 2 0 3614 "3734 114" _null_ _null_ _null_ _null_ _null_ json_to_tsvector_byid _null_ _null_ _null_ )); DESCR("transform json to tsvector"); +DATA(insert OID = 8891 ( dictionary_mapping_to_text PGNSP PGUID 12 100 0 0 0 f f f t f s s 2 0 25 "26 23" _null_ _null_ _null_ _null_ _null_ dictionary_mapping_to_text _null_ _null_ _null_ )); +DESCR("returns text representation of dictionary configuration map"); + +DATA(insert OID = 8892 ( ts_debug PGNSP PGUID 12 100 1 0 0 f f f t t s s 2 0 2249 "3734 25" "{3734,25,25,25,25,3770,25,25,1009}" "{i,i,o,o,o,o,o,o,o}" "{cfgId,inputText,alias,description,token,dictionaries,configuration,command,lexemes}" _null_ _null_ ts_debug _null_ _null_ _null_)); +DESCR("debug function for text search configuration"); + DATA(insert OID = 3752 ( tsvector_update_trigger PGNSP PGUID 12 1 0 0 0 f f f f f v s 0 0 2279 "" _null_ _null_ _null_ _null_ _null_ tsvector_update_trigger_byid _null_ _null_ _null_ )); DESCR("trigger for automatic update of tsvector column"); DATA(insert OID = 3753 ( tsvector_update_trigger_column PGNSP PGUID 12 1 0 0 0 f f f f f v s 0 0 2279 "" _null_ _null_ _null_ _null_ _null_ tsvector_update_trigger_bycolumn _null_ _null_ _null_ )); diff --git a/src/include/catalog/pg_ts_config_map.h b/src/include/catalog/pg_ts_config_map.h index a3d9e3f21f..65a9a73369 100644 --- a/src/include/catalog/pg_ts_config_map.h +++ b/src/include/catalog/pg_ts_config_map.h @@ -22,6 +22,7 @@ #define PG_TS_CONFIG_MAP_H #include "catalog/genbki.h" +#include "utils/jsonb.h" /* ---------------- * pg_ts_config_map definition. cpp turns this into @@ -30,49 +31,114 @@ */ #define TSConfigMapRelationId 3603 +/* + * Create a typedef in order to use same type name in + * generated DB initialization script and C source code + */ +typedef Jsonb jsonb; + CATALOG(pg_ts_config_map,3603) BKI_WITHOUT_OIDS { Oid mapcfg; /* OID of configuration owning this entry */ int32 maptokentype; /* token type from parser */ - int32 mapseqno; /* order in which to consult dictionaries */ - Oid mapdict; /* dictionary to consult */ + + /* + * mapdicts is the only one variable-length field so it is safe to use + * it directly, without hiding from C interface. + */ + jsonb mapdicts; /* dictionary map Jsonb representation */ } FormData_pg_ts_config_map; typedef FormData_pg_ts_config_map *Form_pg_ts_config_map; +/* + * Element of the mapping expression tree + */ +typedef struct TSMapElement +{ + int type; /* Type of the element */ + union + { + struct TSMapExpression *objectExpression; + struct TSMapCase *objectCase; + Oid objectDictionary; + void *object; + } value; + struct TSMapElement *parent; /* Parent in the expression tree */ +} TSMapElement; + +/* + * Representation of expression with operator and two operands + */ +typedef struct TSMapExpression +{ + int operator; + TSMapElement *left; + TSMapElement *right; +} TSMapExpression; + +/* + * Representation of CASE structure inside database + */ +typedef struct TSMapCase +{ + TSMapElement *condition; + TSMapElement *command; + TSMapElement *elsebranch; + bool match; /* If false, NO MATCH is used */ +} TSMapCase; + /* ---------------- - * compiler constants for pg_ts_config_map + * Compiler constants for pg_ts_config_map * ---------------- */ -#define Natts_pg_ts_config_map 4 +#define Natts_pg_ts_config_map 3 #define Anum_pg_ts_config_map_mapcfg 1 #define Anum_pg_ts_config_map_maptokentype 2 -#define Anum_pg_ts_config_map_mapseqno 3 -#define Anum_pg_ts_config_map_mapdict 4 +#define Anum_pg_ts_config_map_mapdicts 3 + +/* ---------------- + * Dictionary map operators + * ---------------- + */ +#define TSMAP_OP_MAP 1 +#define TSMAP_OP_UNION 2 +#define TSMAP_OP_EXCEPT 3 +#define TSMAP_OP_INTERSECT 4 +#define TSMAP_OP_COMMA 5 + +/* ---------------- + * TSMapElement object types + * ---------------- + */ +#define TSMAP_EXPRESSION 1 +#define TSMAP_CASE 2 +#define TSMAP_DICTIONARY 3 +#define TSMAP_KEEP 4 /* ---------------- * initial contents of pg_ts_config_map * ---------------- */ -DATA(insert ( 3748 1 1 3765 )); -DATA(insert ( 3748 2 1 3765 )); -DATA(insert ( 3748 3 1 3765 )); -DATA(insert ( 3748 4 1 3765 )); -DATA(insert ( 3748 5 1 3765 )); -DATA(insert ( 3748 6 1 3765 )); -DATA(insert ( 3748 7 1 3765 )); -DATA(insert ( 3748 8 1 3765 )); -DATA(insert ( 3748 9 1 3765 )); -DATA(insert ( 3748 10 1 3765 )); -DATA(insert ( 3748 11 1 3765 )); -DATA(insert ( 3748 15 1 3765 )); -DATA(insert ( 3748 16 1 3765 )); -DATA(insert ( 3748 17 1 3765 )); -DATA(insert ( 3748 18 1 3765 )); -DATA(insert ( 3748 19 1 3765 )); -DATA(insert ( 3748 20 1 3765 )); -DATA(insert ( 3748 21 1 3765 )); -DATA(insert ( 3748 22 1 3765 )); +DATA(insert ( 3748 1 "[3765]" )); +DATA(insert ( 3748 2 "[3765]" )); +DATA(insert ( 3748 3 "[3765]" )); +DATA(insert ( 3748 4 "[3765]" )); +DATA(insert ( 3748 5 "[3765]" )); +DATA(insert ( 3748 6 "[3765]" )); +DATA(insert ( 3748 7 "[3765]" )); +DATA(insert ( 3748 8 "[3765]" )); +DATA(insert ( 3748 9 "[3765]" )); +DATA(insert ( 3748 10 "[3765]" )); +DATA(insert ( 3748 11 "[3765]" )); +DATA(insert ( 3748 15 "[3765]" )); +DATA(insert ( 3748 16 "[3765]" )); +DATA(insert ( 3748 17 "[3765]" )); +DATA(insert ( 3748 18 "[3765]" )); +DATA(insert ( 3748 19 "[3765]" )); +DATA(insert ( 3748 20 "[3765]" )); +DATA(insert ( 3748 21 "[3765]" )); +DATA(insert ( 3748 22 "[3765]" )); #endif /* PG_TS_CONFIG_MAP_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fce48026b6..1d3896d494 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -385,6 +385,9 @@ typedef enum NodeTag T_CreateEnumStmt, T_CreateRangeStmt, T_AlterEnumStmt, + T_DictMapExprElem, + T_DictMapElem, + T_DictMapCase, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, T_CreateFdwStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 699fa77bc7..6103b12cce 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3434,6 +3434,50 @@ typedef enum AlterTSConfigType ALTER_TSCONFIG_DROP_MAPPING } AlterTSConfigType; +/* + * TS Configuration expression tree element's types + */ +typedef enum DictMapElemType +{ + DICT_MAP_CASE, + DICT_MAP_EXPRESSION, + DICT_MAP_KEEP, + DICT_MAP_DICTIONARY +} DictMapElemType; + +/* + * TS Configuration expression tree abstract element + */ +typedef struct DictMapElem +{ + NodeTag type; + int8 kind; /* See DictMapElemType */ + void *data; /* Type should be detected by kind value */ +} DictMapElem; + +/* + * TS Configuration expression tree element with operator and operands + */ +typedef struct DictMapExprElem +{ + NodeTag type; + DictMapElem *left; + DictMapElem *right; + int8 oper; +} DictMapExprElem; + +/* + * TS Configuration expression tree CASE element + */ +typedef struct DictMapCase +{ + NodeTag type; + struct DictMapElem *condition; + struct DictMapElem *command; + struct DictMapElem *elsebranch; + bool match; +} DictMapCase; + typedef struct AlterTSConfigurationStmt { NodeTag type; @@ -3446,6 +3490,7 @@ typedef struct AlterTSConfigurationStmt */ List *tokentype; /* list of Value strings */ List *dicts; /* list of list of Value strings */ + DictMapElem *dict_map; /* tree of the mapping expression */ bool override; /* if true - remove old variant */ bool replace; /* if true - replace dictionary by another */ bool missing_ok; /* for DROP - skip error if missing? */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 4dff55a8e9..3371f286a8 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -220,6 +220,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("keep", KEEP, RESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) @@ -242,6 +243,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD) +PG_KEYWORD("map", MAP, UNRESERVED_KEYWORD) PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD) diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h index 410f1d54af..4633dd7618 100644 --- a/src/include/tsearch/ts_cache.h +++ b/src/include/tsearch/ts_cache.h @@ -14,6 +14,7 @@ #define TS_CACHE_H #include "utils/guc.h" +#include "catalog/pg_ts_config_map.h" /* @@ -66,6 +67,7 @@ typedef struct { int len; Oid *dictIds; + int32 *dictOptions; } ListDictionary; typedef struct @@ -77,7 +79,7 @@ typedef struct Oid prsId; int lenmap; - ListDictionary *map; + TSMapElement **map; } TSConfigCacheEntry; diff --git a/src/include/tsearch/ts_configmap.h b/src/include/tsearch/ts_configmap.h new file mode 100644 index 0000000000..79e618052e --- /dev/null +++ b/src/include/tsearch/ts_configmap.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.h + * internal representation of text search configuration and utilities for it + * + * Copyright (c) 1998-2018, PostgreSQL Global Development Group + * + * src/include/tsearch/ts_utils.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PG_TS_CONFIGMAP_H_ +#define _PG_TS_CONFIGMAP_H_ + +#include "utils/jsonb.h" +#include "catalog/pg_ts_config_map.h" + +/* + * Configuration storage functions + * Provide interface to convert ts_configuration into JSONB and vice versa + */ + +/* Convert TSMapElement structure into JSONB */ +extern Jsonb *TSMapToJsonb(TSMapElement *config); + +/* Extract TSMapElement from JSONB formated data */ +extern TSMapElement *JsonbToTSMap(Jsonb *json); +/* Replace all occurances of oldDict by newDict */ +extern void TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict); + +/* Move rule list into specified memory context */ +extern TSMapElement *TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context); +/* Free all nodes of the rule list */ +extern void TSMapElementFree(TSMapElement *element); + +/* Print map in human-readable format */ +extern void TSMapPrintElement(TSMapElement *config, StringInfo result); + +/* Print dictionary name for a given Oid */ +extern void TSMapPrintDictName(Oid dictId, StringInfo result); + +/* Return all dictionaries used in config */ +extern Oid *TSMapGetDictionaries(TSMapElement *config); + +/* Do a deep comparison of two TSMapElements. Doesn't check parents of elements */ +extern bool TSMapElementEquals(TSMapElement *a, TSMapElement *b); + +#endif /* _PG_TS_CONFIGMAP_H_ */ diff --git a/src/include/tsearch/ts_public.h b/src/include/tsearch/ts_public.h index 0b7a5aa68e..d970eec0ab 100644 --- a/src/include/tsearch/ts_public.h +++ b/src/include/tsearch/ts_public.h @@ -115,6 +115,7 @@ typedef struct #define TSL_ADDPOS 0x01 #define TSL_PREFIX 0x02 #define TSL_FILTER 0x04 +#define TSL_MULTI 0x08 /* * Struct for supporting complex dictionaries like thesaurus. diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index d56c70c847..08c2674d46 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -1089,14 +1089,6 @@ WHERE mapcfg != 0 AND ------+-------- (0 rows) -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); - ctid | mapdict -------+--------- -(0 rows) - SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/expected/tsdicts.out b/src/test/regress/expected/tsdicts.out index 0c1d7c7675..512af5975e 100644 --- a/src/test/regress/expected/tsdicts.out +++ b/src/test/regress/expected/tsdicts.out @@ -420,6 +420,105 @@ SELECT ts_lexize('thesaurus', 'one'); {1} (1 row) +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; +SELECT to_tsvector('english_union', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_union', 'books'); + to_tsvector +-------------------- + 'book':1 'books':1 +(1 row) + +SELECT to_tsvector('english_union', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; +SELECT to_tsvector('english_intersect', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_intersect', 'books'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_intersect', 'booking'); + to_tsvector +------------- + +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; +SELECT to_tsvector('english_except', 'book'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_except', 'books'); + to_tsvector +------------- + 'books':1 +(1 row) + +SELECT to_tsvector('english_except', 'booking'); + to_tsvector +------------- + 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; +SELECT to_tsvector('english_branches', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'books'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -580,6 +679,163 @@ SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a 'card':3,10 'invit':2,9 'like':6 'look':5 'order':1,8 (1 row) +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +--------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +----------------------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 'supernova':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +------------------------------------------------------------------------ + '1987a':6 'mysterious':2 'of':4 'rings':3 'sn':5 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------------------- + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN MATCH THEN simple UNION thesaurus+ + | ELSE simple + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------------------ + '12':1 'one':1 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +----------------------------------- + '123':1 'one':1 'three':3 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two four'); + to_tsvector +--------------------------------- + '12':1 'four':3 'one':1 'two':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------ + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN NO MATCH THEN simple+ + | ELSE thesaurus + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +------------------ + '12':1 'books':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +----------------- + '12':1 'book':2 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION operators_tst ( + COPY=thesaurus_tst +); +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +-------------------------------------------------------------------------------------- + '1987a':6 'mysteri':2 'mysterious':2 'of':4 'ring':3 'rings':3 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION (synonym, simple); +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A Postgres'); + to_tsvector +----------------------------------------------------------------------------------------------------------- + '1987a':6 'mysteri':2 'mysterious':2 'of':4 'pgsql':7 'postgr':7 'ring':3 'rings':3 'supernova':5 'the':1 +(1 row) + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index d63fb12f1d..c0e9fc5c8f 100644 --- a/src/test/regress/expected/tsearch.out +++ b/src/test/regress/expected/tsearch.out @@ -36,11 +36,11 @@ WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -----+--------- (0 rows) -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; - mapcfg | maptokentype | mapseqno ---------+--------------+---------- +WHERE mapcfg = 0; + mapcfg | maptokentype +--------+-------------- (0 rows) -- Look for pg_ts_config_map entries that aren't one of parser's token types @@ -51,8 +51,8 @@ RIGHT JOIN pg_ts_config_map AS m ON (tt.cfgid=m.mapcfg AND tt.tokid=m.maptokentype) WHERE tt.cfgid IS NULL OR tt.tokid IS NULL; - cfgid | tokid | mapcfg | maptokentype | mapseqno | mapdict --------+-------+--------+--------------+----------+--------- + cfgid | tokid | mapcfg | maptokentype | mapdicts +-------+-------+--------+--------------+---------- (0 rows) -- test basic text search behavior without indexes, then with @@ -567,55 +567,55 @@ SELECT length(to_tsvector('english', '345 [email protected] '' http://www.com/ http://ae -- ts_debug SELECT * from ts_debug('english', '<myns:foo-bar_baz.blurfl>abc&nm1;def©ghiõjkl</myns:foo-bar_baz.blurfl>'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+----------------------------+----------------+--------------+--------- - tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | - asciiword | Word, all ASCII | abc | {english_stem} | english_stem | {abc} - entity | XML entity | &nm1; | {} | | - asciiword | Word, all ASCII | def | {english_stem} | english_stem | {def} - entity | XML entity | © | {} | | - asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | {ghi} - entity | XML entity | õ | {} | | - asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | {jkl} - tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+----------------------------+----------------+---------------+--------------+--------- + tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | | + asciiword | Word, all ASCII | abc | {english_stem} | english_stem | english_stem | {abc} + entity | XML entity | &nm1; | {} | | | + asciiword | Word, all ASCII | def | {english_stem} | english_stem | english_stem | {def} + entity | XML entity | © | {} | | | + asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | english_stem | {ghi} + entity | XML entity | õ | {} | | | + asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | english_stem | {jkl} + tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | | (9 rows) -- check parsing of URLs SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx</span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------------------+--------------+------------+------------------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | {www.harewoodsolutions.co.uk/press.aspx} - host | Host | www.harewoodsolutions.co.uk | {simple} | simple | {www.harewoodsolutions.co.uk} - url_path | URL path | /press.aspx | {simple} | simple | {/press.aspx} - tag | XML tag | </span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------------------+--------------+---------------+---------+------------------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | simple | {www.harewoodsolutions.co.uk/press.aspx} + host | Host | www.harewoodsolutions.co.uk | {simple} | simple | simple | {www.harewoodsolutions.co.uk} + url_path | URL path | /press.aspx | {simple} | simple | simple | {/press.aspx} + tag | XML tag | </span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw<span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------+--------------+------------+------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | {aew.wer0c.ewr/id?ad=qwe&dw} - host | Host | aew.wer0c.ewr | {simple} | simple | {aew.wer0c.ewr} - url_path | URL path | /id?ad=qwe&dw | {simple} | simple | {/id?ad=qwe&dw} - tag | XML tag | <span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------+--------------+---------------+---------+------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | simple | {aew.wer0c.ewr/id?ad=qwe&dw} + host | Host | aew.wer0c.ewr | {simple} | simple | simple | {aew.wer0c.ewr} + url_path | URL path | /id?ad=qwe&dw | {simple} | simple | simple | {/id?ad=qwe&dw} + tag | XML tag | <span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://5aew.werc.ewr:8100/?'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------+--------------+------------+------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | {5aew.werc.ewr:8100/?} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /? | {simple} | simple | {/?} + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------+--------------+---------------+---------+------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | simple | {5aew.werc.ewr:8100/?} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /? | {simple} | simple | simple | {/?} (4 rows) SELECT * from ts_debug('english', '5aew.werc.ewr:8100/?xx'); - alias | description | token | dictionaries | dictionary | lexemes -----------+-------------+------------------------+--------------+------------+-------------------------- - url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | {5aew.werc.ewr:8100/?xx} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /?xx | {simple} | simple | {/?xx} + alias | description | token | dictionaries | configuration | command | lexemes +----------+-------------+------------------------+--------------+---------------+---------+-------------------------- + url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | simple | {5aew.werc.ewr:8100/?xx} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /?xx | {simple} | simple | simple | {/?xx} (3 rows) SELECT token, alias, diff --git a/src/test/regress/sql/oidjoins.sql b/src/test/regress/sql/oidjoins.sql index 656cace451..4e6730fa69 100644 --- a/src/test/regress/sql/oidjoins.sql +++ b/src/test/regress/sql/oidjoins.sql @@ -545,10 +545,6 @@ SELECT ctid, mapcfg FROM pg_catalog.pg_ts_config_map fk WHERE mapcfg != 0 AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_config pk WHERE pk.oid = fk.mapcfg); -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/sql/tsdicts.sql b/src/test/regress/sql/tsdicts.sql index 1633c0d066..080ddc486a 100644 --- a/src/test/regress/sql/tsdicts.sql +++ b/src/test/regress/sql/tsdicts.sql @@ -117,6 +117,57 @@ CREATE TEXT SEARCH DICTIONARY thesaurus ( SELECT ts_lexize('thesaurus', 'one'); +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; + +SELECT to_tsvector('english_union', 'book'); +SELECT to_tsvector('english_union', 'books'); +SELECT to_tsvector('english_union', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; + +SELECT to_tsvector('english_intersect', 'book'); +SELECT to_tsvector('english_intersect', 'books'); +SELECT to_tsvector('english_intersect', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; + +SELECT to_tsvector('english_except', 'book'); +SELECT to_tsvector('english_except', 'books'); +SELECT to_tsvector('english_except', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; + +SELECT to_tsvector('english_branches', 'book'); +SELECT to_tsvector('english_branches', 'books'); +SELECT to_tsvector('english_branches', 'booking'); + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -189,6 +240,50 @@ SELECT to_tsvector('thesaurus_tst', 'one postgres one two one two three one'); SELECT to_tsvector('thesaurus_tst', 'Supernovae star is very new star and usually called supernovae (abbreviation SN)'); SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a tickets'); +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two four'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + +CREATE TEXT SEARCH CONFIGURATION operators_tst ( + COPY=thesaurus_tst +); + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION (synonym, simple); +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A Postgres'); + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index 1c8520b3e9..6f8af63c1a 100644 --- a/src/test/regress/sql/tsearch.sql +++ b/src/test/regress/sql/tsearch.sql @@ -26,9 +26,9 @@ SELECT oid, cfgname FROM pg_ts_config WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; +WHERE mapcfg = 0; -- Look for pg_ts_config_map entries that aren't one of parser's token types SELECT * FROM ^ permalink raw reply [nested|flat] 265+ messages in thread
* Re: Flexible configuration for full-text search @ 2018-04-09 12:07 Aleksandr Parfenov <[email protected]> parent: Aleksandr Parfenov <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Aleksandr Parfenov @ 2018-04-09 12:07 UTC (permalink / raw) To: Teodor Sigaev <[email protected]>; +Cc: Aleksander Alekseev <[email protected]>; [email protected] Hi, After last commits related to storing initial data-set of catalog and commits related to MERGE command with changes in gram.y the patch doesn't apply. A rebased in the attachment. -- Aleksandr Parfenov Postgres Professional: http://www.postgrespro.com Russian Postgres Company Attachments: [text/x-patch] 0001-flexible-fts-configuration-v12.patch (178.4K, ../../20180409150715.40ec7d02@asp437-24-g082ur/2-0001-flexible-fts-configuration-v12.patch) download | inline diff: diff --git a/contrib/unaccent/expected/unaccent.out b/contrib/unaccent/expected/unaccent.out index b93105e..37b9337 100644 --- a/contrib/unaccent/expected/unaccent.out +++ b/contrib/unaccent/expected/unaccent.out @@ -61,3 +61,14 @@ SELECT ts_lexize('unaccent', ' {����} (1 row) +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; +SELECT to_tsvector('unaccent', 'foobar ����� ����'); + to_tsvector +------------------------------ + 'foobar':1 '�����':2 '���':3 +(1 row) + diff --git a/contrib/unaccent/sql/unaccent.sql b/contrib/unaccent/sql/unaccent.sql index 3102139..6ce21cd 100644 --- a/contrib/unaccent/sql/unaccent.sql +++ b/contrib/unaccent/sql/unaccent.sql @@ -2,7 +2,6 @@ CREATE EXTENSION unaccent; -- must have a UTF8 database SELECT getdatabaseencoding(); - SET client_encoding TO 'KOI8'; SELECT unaccent('foobar'); @@ -16,3 +15,12 @@ SELECT unaccent('unaccent', ' SELECT ts_lexize('unaccent', 'foobar'); SELECT ts_lexize('unaccent', '����'); SELECT ts_lexize('unaccent', '����'); + +CREATE TEXT SEARCH CONFIGURATION unaccent( + COPY=russian +); + +ALTER TEXT SEARCH CONFIGURATION unaccent ALTER MAPPING FOR + asciiword, word WITH unaccent MAP russian_stem; + +SELECT to_tsvector('unaccent', 'foobar ����� ����'); diff --git a/doc/src/sgml/ref/alter_tsconfig.sgml b/doc/src/sgml/ref/alter_tsconfig.sgml index ebe0b94..ecc3704 100644 --- a/doc/src/sgml/ref/alter_tsconfig.sgml +++ b/doc/src/sgml/ref/alter_tsconfig.sgml @@ -22,8 +22,12 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ADD MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> + ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">config</replaceable> +ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ALTER MAPPING FOR <replaceable class="parameter">token_type</replaceable> [, ... ] WITH <replaceable class="parameter">dictionary_name</replaceable> [, ... ] ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> ALTER MAPPING REPLACE <replaceable class="parameter">old_dictionary</replaceable> WITH <replaceable class="parameter">new_dictionary</replaceable> @@ -89,6 +93,17 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </varlistentry> <varlistentry> + <term><replaceable class="parameter">config</replaceable></term> + <listitem> + <para> + The dictionaries tree expression. The dictionary expression + is a triple of condition/command/else that define way to process + the text. The <literal>ELSE</literal> part is optional. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable class="parameter">old_dictionary</replaceable></term> <listitem> <para> @@ -133,7 +148,7 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </para> </listitem> </varlistentry> - </variablelist> + </variablelist> <para> The <literal>ADD MAPPING FOR</literal> form installs a list of dictionaries to be @@ -155,6 +170,53 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl </refsect1> <refsect1> + <title>Dictionaries Map Configuration</title> + + <refsect2> + <title>Format</title> + <para> + Formally <replaceable class="parameter">config</replaceable> is one of: + </para> + <programlisting> + * dictionary_name + + * config { UNION | INTERSECT | EXCEPT | MAP } config + + * CASE config + WHEN [ NO ] MATCH THEN { KEEP | config } + [ ELSE config ] + END + </programlisting> + </refsect2> + + <refsect2> + <title>Description</title> + <para> + <replaceable class="parameter">config</replaceable> can be used + in three different formats. The most simple format is name of dictionary to + use for tokens processing. + </para> + <para> + In order to use more than one dictionary + simultaneously user should interconnect dictionaries by operators. Operators + <literal>UNION</literal>, <literal>EXCEPT</literal> and + <literal>INTERSECT</literal> have same meaning as in operations on sets. + Special operator <literal>MAP</literal> gets output of left subexpression + and uses it as an input to right subexpression. + </para> + <para> + The third format of <replaceable class="parameter">config</replaceable> is similar to + <literal>CASE/WHEN/THEN/ELSE</literal> structure. It's consists of three + replaceable parts. First one is configuration which is used to construct lexemes set + for matching condition. If the condition is triggered, the command is executed. + Use command <literal>KEEP</literal> to avoid repeating of the same + configuration in condition and command part. However, command may differ from + the condition. The <literal>ELSE</literal> branch is executed otherwise. + </para> + </refsect2> + </refsect1> + + <refsect1> <title>Examples</title> <para> @@ -167,6 +229,34 @@ ALTER TEXT SEARCH CONFIGURATION <replaceable>name</replaceable> SET SCHEMA <repl ALTER TEXT SEARCH CONFIGURATION my_config ALTER MAPPING REPLACE english WITH swedish; </programlisting> + + <para> + Next example shows how to analyse documents in both English and German languages. + <literal>english_hunspell</literal> and <literal>german_hunspell</literal> + return result only if a word is recognized. Otherwise, stemmer dictionaries + are used to process a token. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + In order to combine search for both exact and processed forms the vector + should contain lexemes produced by <literal>simple</literal> for exact form + of the word as well as lexemes produced by linguistic-aware dictionary + (e.g. <literal>english_stem</literal>) for processed forms. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION my_config + ALTER MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + </refsect1> <refsect1> diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 19f5851..de14dae 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -732,10 +732,11 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); The <function>to_tsvector</function> function internally calls a parser which breaks the document text into tokens and assigns a type to each token. For each token, a list of - dictionaries (<xref linkend="textsearch-dictionaries"/>) is consulted, - where the list can vary depending on the token type. The first dictionary - that <firstterm>recognizes</firstterm> the token emits one or more normalized - <firstterm>lexemes</firstterm> to represent the token. For example, + condition/command pairs is consulted, where the list can vary depending + on the token type, condition and command are expressions on dictionaries + with matching clause in condition(<xref linkend="textsearch-dictionaries"/>). + The first command combined with true-resulted condition emits one or more normalized + <firstterm>lexemes</firstterm> to represent the token. For example, <literal>rats</literal> became <literal>rat</literal> because one of the dictionaries recognized that the word <literal>rats</literal> is a plural form of <literal>rat</literal>. Some words are recognized as @@ -743,7 +744,7 @@ SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); causes them to be ignored since they occur too frequently to be useful in searching. In our example these are <literal>a</literal>, <literal>on</literal>, and <literal>it</literal>. - If no dictionary in the list recognizes the token then it is also ignored. + If none of conditions is <literal>true</literal> the token is ignored. In this example that happened to the punctuation sign <literal>-</literal> because there are in fact no dictionaries assigned for its token type (<literal>Space symbols</literal>), meaning space tokens will never be @@ -2316,8 +2317,8 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h <para> a single lexeme with the <literal>TSL_FILTER</literal> flag set, to replace the original token with a new token to be passed to subsequent - dictionaries (a dictionary that does this is called a - <firstterm>filtering dictionary</firstterm>) + dictionaries in a comma-separated syntax (a dictionary that does this + is called a <firstterm>filtering dictionary</firstterm>) </para> </listitem> <listitem> @@ -2349,38 +2350,126 @@ SELECT alias, description, token FROM ts_debug('http://example.com/stuff/index.h type that the parser can return, a separate list of dictionaries is specified by the configuration. When a token of that type is found by the parser, each dictionary in the list is consulted in turn, - until some dictionary recognizes it as a known word. If it is identified - as a stop word, or if no dictionary recognizes the token, it will be - discarded and not indexed or searched for. - Normally, the first dictionary that returns a non-<literal>NULL</literal> - output determines the result, and any remaining dictionaries are not - consulted; but a filtering dictionary can replace the given word - with a modified word, which is then passed to subsequent dictionaries. + until command is not selected based on its condition. If none of cases is + selected token will be discarded and not indexed or searched for. </para> <para> - The general rule for configuring a list of dictionaries - is to place first the most narrow, most specific dictionary, then the more - general dictionaries, finishing with a very general dictionary, like + A tree of cases is described as condition/command/else triples. Each + condition is evaluated in order to select appropriate command to generate + resulted set of lexemes. + </para> + + <para> + A condition is an expression with dictionaries used as operands and + basic set operators <literal>UNION</literal>, <literal>EXCEPT</literal>, <literal>INTERSECT</literal> + and special operator <literal>MAP</literal>. + Special operator <literal>MAP</literal> use output of left subexpression as + input for right subexpression. + </para> + + <para> + Rules to write command are same as for condition with additional keyword + <literal>KEEP</literal> considered to use the result of the condition as an output. + </para> + + <para> + A comma-separated list of dictionaries is a simplified variant of text + search configuration. Each dictionary consulted to process a token and first + non-<literal>NULL</literal> output is accepted as a processing result. + </para> + + <para> + The general rule for configuring tokens processing + is to place first case with the most narrow, most specific dictionary, then the more + general dictionaries, finishing with a very general dictionaries, like a <application>Snowball</application> stemmer or <literal>simple</literal>, which - recognizes everything. For example, for an astronomy-specific search + recognizes everything. For example, for an astronomy-specific search (<literal>astro_en</literal> configuration) one could bind token type <type>asciiword</type> (ASCII word) to a synonym dictionary of astronomical terms, a general English dictionary and a <application>Snowball</application> English - stemmer: + stemmer in comma-separated variant of mapping: + </para> <programlisting> ALTER TEXT SEARCH CONFIGURATION astro_en ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem; </programlisting> + + <para> + Another example is a configuration for both English and German languages via + operator-separated variant of mapping: + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION multi_en_de + ADD MAPPING FOR asciiword, word WITH + CASE english_hunspell WHEN MATCH THEN KEEP ELSE english_stem END + UNION + CASE german_hunspell WHEN MATCH THEN KEEP ELSE german_stem END; +</programlisting> + + <para> + This configuration provides an ability to search on collection of multilingual + documents without specifying language: + </para> + +<programlisting> +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'lack'); + id | txt +----+----------------------------------------- + 2 | with old stars and lacking gas and dust + +WITH docs(id, txt) as (values (1, 'Das geschah zu Beginn dieses Monats'), + (2, 'with old stars and lacking gas and dust'), + (3, '25 light-years across, blown bywinds from its central')) +SELECT * FROM docs WHERE to_tsvector('multi_en_de', txt) @@ to_tsquery('multi_en_de', 'beginnen'); + id | txt +----+------------------------------------- + 1 | Das geschah zu Beginn dieses Monats +</programlisting> + + <para> + A combination of stemmer dictionary with <literal>simple</literal> one may be used to mix + search for exact form of one word and linguistic search for others. + </para> + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION exact_and_linguistic + ADD MAPPING FOR asciiword, word WITH english_stem UNION simple; +</programlisting> + + <para> + In the following example a simple dictionary is used to prevent words from normalization in query. </para> +<programlisting> +WITH docs(id, txt) as (values (1, 'Supernova star'), + (2, 'Supernova stars')) +SELECT * FROM docs WHERE to_tsvector('exact_and_linguistic', txt) @@ (to_tsquery('simple', 'stars') && to_tsquery('english', 'supernovae')); + id | txt +----+----------------- + 2 | Supernova stars +</programlisting> + + <caution> + <para> + Due to lack of information about origin of each lexeme in <literal>tsvector</literal> may + lead to false-positive triggers in case of stemmed form being used as exact form in a query. + </para> + </caution> + <para> - A filtering dictionary can be placed anywhere in the list, except at the - end where it'd be useless. Filtering dictionaries are useful to partially + Filtering dictionaries are useful to partially normalize words to simplify the task of later dictionaries. For example, a filtering dictionary could be used to remove accents from accented letters, as is done by the <xref linkend="unaccent"/> module. + Filter dictionary should be placed at left of <literal>MAP</literal> + operator. If filter dictionary returns <literal>NULL</literal> it pass initial token + to the right subexpression. </para> <sect2 id="textsearch-stopwords"> @@ -2547,9 +2636,9 @@ SELECT ts_lexize('public.simple_dict','The'); <screen> SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | english_stem | {pari} CREATE TEXT SEARCH DICTIONARY my_synonym ( TEMPLATE = synonym, @@ -2561,9 +2650,12 @@ ALTER TEXT SEARCH CONFIGURATION english WITH my_synonym, english_stem; SELECT * FROM ts_debug('english', 'Paris'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+---------------------------+------------+--------- - asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+---------------------------+---------------------------------------------+------------+--------- + asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | CASE my_synonym WHEN MATCH THEN KEEP +| my_synonym | {paris} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> </para> @@ -3192,6 +3284,21 @@ CREATE TEXT SEARCH DICTIONARY english_ispell ( ALTER TEXT SEARCH CONFIGURATION pg ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part + WITH + CASE pg_dict WHEN MATCH THEN KEEP + ELSE + CASE english_ispell WHEN MATCH THEN KEEP + ELSE english_stem + END + END; +</programlisting> + + Or use alternative comma-separated syntax: + +<programlisting> +ALTER TEXT SEARCH CONFIGURATION pg + ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, + word, hword, hword_part WITH pg_dict, english_ispell, english_stem; </programlisting> @@ -3267,7 +3374,8 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re OUT <replaceable class="parameter">description</replaceable> <type>text</type>, OUT <replaceable class="parameter">token</replaceable> <type>text</type>, OUT <replaceable class="parameter">dictionaries</replaceable> <type>regdictionary[]</type>, - OUT <replaceable class="parameter">dictionary</replaceable> <type>regdictionary</type>, + OUT <replaceable class="parameter">configuration</replaceable> <type>text</type>, + OUT <replaceable class="parameter">command</replaceable> <type>text</type>, OUT <replaceable class="parameter">lexemes</replaceable> <type>text[]</type>) returns setof record </synopsis> @@ -3311,14 +3419,20 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re </listitem> <listitem> <para> - <replaceable>dictionary</replaceable> <type>regdictionary</type> — the dictionary - that recognized the token, or <literal>NULL</literal> if none did + <replaceable>configuration</replaceable> <type>text</type> — the + configuration defined for this token type + </para> + </listitem> + <listitem> + <para> + <replaceable>command</replaceable> <type>text</type> — the command that describes + the way the output was produced </para> </listitem> <listitem> <para> <replaceable>lexemes</replaceable> <type>text[]</type> — the lexeme(s) produced - by the dictionary that recognized the token, or <literal>NULL</literal> if + by the command selected according conditions, or <literal>NULL</literal> if none did; an empty array (<literal>{}</literal>) means it was recognized as a stop word </para> @@ -3331,32 +3445,32 @@ ts_debug(<optional> <replaceable class="parameter">config</replaceable> <type>re <screen> SELECT * FROM ts_debug('english','a fat cat sat on a mat - it ate a fat rats'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------+----------------+--------------+--------- - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | cat | {english_stem} | english_stem | {cat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | sat | {english_stem} | english_stem | {sat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | on | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | mat | {english_stem} | english_stem | {mat} - blank | Space symbols | | {} | | - blank | Space symbols | - | {} | | - asciiword | Word, all ASCII | it | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | ate | {english_stem} | english_stem | {ate} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | a | {english_stem} | english_stem | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | fat | {english_stem} | english_stem | {fat} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | rats | {english_stem} | english_stem | {rat} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------+----------------+---------------+--------------+--------- + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | cat | {english_stem} | english_stem | english_stem | {cat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | sat | {english_stem} | english_stem | english_stem | {sat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | on | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | mat | {english_stem} | english_stem | english_stem | {mat} + blank | Space symbols | | | | | + blank | Space symbols | - | | | | + asciiword | Word, all ASCII | it | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | ate | {english_stem} | english_stem | english_stem | {ate} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | a | {english_stem} | english_stem | english_stem | {} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | fat | {english_stem} | english_stem | english_stem | {fat} + blank | Space symbols | | | | | + asciiword | Word, all ASCII | rats | {english_stem} | english_stem | english_stem | {rat} </screen> </para> @@ -3382,13 +3496,22 @@ ALTER TEXT SEARCH CONFIGURATION public.english <screen> SELECT * FROM ts_debug('public.english','The Brightest supernovaes'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+-------------+-------------------------------+----------------+------------- - asciiword | Word, all ASCII | The | {english_ispell,english_stem} | english_ispell | {} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | english_ispell | {bright} - blank | Space symbols | | {} | | - asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | english_stem | {supernova} + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+-------------+-------------------------------+---------------------------------------------+------------------+------------- + asciiword | Word, all ASCII | The | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | Brightest | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_ispell | {bright} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | + blank | Space symbols | | | | | + asciiword | Word, all ASCII | supernovaes | {english_ispell,english_stem} | CASE english_ispell WHEN MATCH THEN KEEP +| english_stem | {supernova} + | | | | ELSE CASE english_stem WHEN MATCH THEN KEEP+| | + | | | | END +| | + | | | | END | | </screen> <para> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 85a17a4..a3da25e 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -948,55 +948,14 @@ GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublicatio -- Tsearch debug function. Defined here because it'd be pretty unwieldy -- to put it into pg_proc.h -CREATE FUNCTION ts_debug(IN config regconfig, IN document text, - OUT alias text, - OUT description text, - OUT token text, - OUT dictionaries regdictionary[], - OUT dictionary regdictionary, - OUT lexemes text[]) -RETURNS SETOF record AS -$$ -SELECT - tt.alias AS alias, - tt.description AS description, - parse.token AS token, - ARRAY ( SELECT m.mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY m.mapseqno ) - AS dictionaries, - ( SELECT mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS dictionary, - ( SELECT pg_catalog.ts_lexize(mapdict, parse.token) - FROM pg_catalog.pg_ts_config_map AS m - WHERE m.mapcfg = $1 AND m.maptokentype = parse.tokid - ORDER BY pg_catalog.ts_lexize(mapdict, parse.token) IS NULL, m.mapseqno - LIMIT 1 - ) AS lexemes -FROM pg_catalog.ts_parse( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ), $2 - ) AS parse, - pg_catalog.ts_token_type( - (SELECT cfgparser FROM pg_catalog.pg_ts_config WHERE oid = $1 ) - ) AS tt -WHERE tt.tokid = parse.tokid -$$ -LANGUAGE SQL STRICT STABLE PARALLEL SAFE; - -COMMENT ON FUNCTION ts_debug(regconfig,text) IS - 'debug function for text search configuration'; CREATE FUNCTION ts_debug(IN document text, OUT alias text, OUT description text, OUT token text, OUT dictionaries regdictionary[], - OUT dictionary regdictionary, + OUT configuration text, + OUT command text, OUT lexemes text[]) RETURNS SETOF record AS $$ diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 3a84351..53ee576 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -39,9 +39,12 @@ #include "nodes/makefuncs.h" #include "parser/parse_func.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_public.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" @@ -935,11 +938,22 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + TSMapElement *mapdicts = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + Oid *dictionaryOids = TSMapGetDictionaries(mapdicts); + Oid *currentOid = dictionaryOids; - referenced.classId = TSDictionaryRelationId; - referenced.objectId = cfgmap->mapdict; - referenced.objectSubId = 0; - add_exact_object_address(&referenced, addrs); + while (*currentOid != InvalidOid) + { + referenced.classId = TSDictionaryRelationId; + referenced.objectId = *currentOid; + referenced.objectSubId = 0; + add_exact_object_address(&referenced, addrs); + + currentOid++; + } + + pfree(dictionaryOids); + TSMapElementFree(mapdicts); } systable_endscan(scan); @@ -1091,8 +1105,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied) mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid; mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype; - mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno; - mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict; + mapvalues[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(&cfgmap->mapdicts); newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls); @@ -1195,7 +1208,7 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt) relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock); /* Add or drop mappings */ - if (stmt->dicts) + if (stmt->dicts || stmt->dict_map) MakeConfigurationMapping(stmt, tup, relMap); else if (stmt->tokentype) DropConfigurationMapping(stmt, tup, relMap); @@ -1271,6 +1284,59 @@ getTokenTypes(Oid prsId, List *tokennames) } /* + * Parse parse node extracted from dictionary mapping and transform it into + * internal representation of dictionary mapping. + */ +static TSMapElement * +ParseTSMapConfig(DictMapElem *elem) +{ + TSMapElement *result = palloc0(sizeof(TSMapElement)); + + if (elem->kind == DICT_MAP_CASE) + { + TSMapCase *caseObject = palloc0(sizeof(TSMapCase)); + DictMapCase *caseASTObject = elem->data; + + caseObject->condition = ParseTSMapConfig(caseASTObject->condition); + caseObject->command = ParseTSMapConfig(caseASTObject->command); + + if (caseASTObject->elsebranch) + caseObject->elsebranch = ParseTSMapConfig(caseASTObject->elsebranch); + + caseObject->match = caseASTObject->match; + + caseObject->condition->parent = result; + caseObject->command->parent = result; + + result->type = TSMAP_CASE; + result->value.objectCase = caseObject; + } + else if (elem->kind == DICT_MAP_EXPRESSION) + { + TSMapExpression *expression = palloc0(sizeof(TSMapExpression)); + DictMapExprElem *expressionAST = elem->data; + + expression->left = ParseTSMapConfig(expressionAST->left); + expression->right = ParseTSMapConfig(expressionAST->right); + expression->operator = expressionAST->oper; + + result->type = TSMAP_EXPRESSION; + result->value.objectExpression = expression; + } + else if (elem->kind == DICT_MAP_KEEP) + { + result->value.objectExpression = NULL; + result->type = TSMAP_KEEP; + } + else if (elem->kind == DICT_MAP_DICTIONARY) + { + result->value.objectDictionary = get_ts_dict_oid(elem->data, false); + result->type = TSMAP_DICTIONARY; + } + return result; +} + +/* * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING */ static void @@ -1286,8 +1352,9 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, Oid prsId; int *tokens, ntoken; - Oid *dictIds; - int ndict; + Oid *dictIds = NULL; + int ndict = 0; + TSMapElement *config = NULL; ListCell *c; prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser; @@ -1326,15 +1393,18 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Convert list of dictionary names to array of dict OIDs */ - ndict = list_length(stmt->dicts); - dictIds = (Oid *) palloc(sizeof(Oid) * ndict); - i = 0; - foreach(c, stmt->dicts) + if (stmt->dicts) { - List *names = (List *) lfirst(c); + ndict = list_length(stmt->dicts); + dictIds = (Oid *) palloc(sizeof(Oid) * ndict); + i = 0; + foreach(c, stmt->dicts) + { + List *names = (List *) lfirst(c); - dictIds[i] = get_ts_dict_oid(names, false); - i++; + dictIds[i] = get_ts_dict_oid(names, false); + i++; + } } if (stmt->replace) @@ -1356,6 +1426,10 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, while (HeapTupleIsValid((maptup = systable_getnext(scan)))) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + Datum repl_val[Natts_pg_ts_config_map]; + bool repl_null[Natts_pg_ts_config_map]; + bool repl_repl[Natts_pg_ts_config_map]; + HeapTuple newtup; /* * check if it's one of target token types @@ -1379,25 +1453,21 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * replace dictionary if match */ - if (cfgmap->mapdict == dictOld) - { - Datum repl_val[Natts_pg_ts_config_map]; - bool repl_null[Natts_pg_ts_config_map]; - bool repl_repl[Natts_pg_ts_config_map]; - HeapTuple newtup; - - memset(repl_val, 0, sizeof(repl_val)); - memset(repl_null, false, sizeof(repl_null)); - memset(repl_repl, false, sizeof(repl_repl)); - - repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew); - repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true; - - newtup = heap_modify_tuple(maptup, - RelationGetDescr(relMap), - repl_val, repl_null, repl_repl); - CatalogTupleUpdate(relMap, &newtup->t_self, newtup); - } + config = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + TSMapReplaceDictionary(config, dictOld, dictNew); + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + repl_val[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); + repl_repl[Anum_pg_ts_config_map_mapdicts - 1] = true; + + newtup = heap_modify_tuple(maptup, + RelationGetDescr(relMap), + repl_val, repl_null, repl_repl); + CatalogTupleUpdate(relMap, &newtup->t_self, newtup); + pfree(config); } systable_endscan(scan); @@ -1407,24 +1477,22 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, /* * Insertion of new entries */ + config = ParseTSMapConfig(stmt->dict_map); + for (i = 0; i < ntoken; i++) { - for (j = 0; j < ndict; j++) - { - Datum values[Natts_pg_ts_config_map]; - bool nulls[Natts_pg_ts_config_map]; + Datum values[Natts_pg_ts_config_map]; + bool nulls[Natts_pg_ts_config_map]; - memset(nulls, false, sizeof(nulls)); - values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); - values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); - values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1); - values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]); + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); + values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); + values[Anum_pg_ts_config_map_mapdicts - 1] = JsonbPGetDatum(TSMapToJsonb(config)); - tup = heap_form_tuple(relMap->rd_att, values, nulls); - CatalogTupleInsert(relMap, tup); + tup = heap_form_tuple(relMap->rd_att, values, nulls); + CatalogTupleInsert(relMap, tup); - heap_freetuple(tup); - } + heap_freetuple(tup); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index b856fe2..34c4295 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4490,6 +4490,42 @@ _copyReassignOwnedStmt(const ReassignOwnedStmt *from) return newnode; } +static DictMapElem * +_copyDictMapElem(const DictMapElem *from) +{ + DictMapElem *newnode = makeNode(DictMapElem); + + COPY_SCALAR_FIELD(kind); + COPY_NODE_FIELD(data); + + return newnode; +} + +static DictMapExprElem * +_copyDictMapExprElem(const DictMapExprElem *from) +{ + DictMapExprElem *newnode = makeNode(DictMapExprElem); + + COPY_NODE_FIELD(left); + COPY_NODE_FIELD(right); + COPY_SCALAR_FIELD(oper); + + return newnode; +} + +static DictMapCase * +_copyDictMapCase(const DictMapCase *from) +{ + DictMapCase *newnode = makeNode(DictMapCase); + + COPY_NODE_FIELD(condition); + COPY_NODE_FIELD(command); + COPY_NODE_FIELD(elsebranch); + COPY_SCALAR_FIELD(match); + + return newnode; +} + static AlterTSDictionaryStmt * _copyAlterTSDictionaryStmt(const AlterTSDictionaryStmt *from) { @@ -5512,6 +5548,15 @@ copyObjectImpl(const void *from) case T_ReassignOwnedStmt: retval = _copyReassignOwnedStmt(from); break; + case T_DictMapExprElem: + retval = _copyDictMapExprElem(from); + break; + case T_DictMapElem: + retval = _copyDictMapElem(from); + break; + case T_DictMapCase: + retval = _copyDictMapCase(from); + break; case T_AlterTSDictionaryStmt: retval = _copyAlterTSDictionaryStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 3994695..48a5a7e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2222,6 +2222,36 @@ _equalReassignOwnedStmt(const ReassignOwnedStmt *a, const ReassignOwnedStmt *b) } static bool +_equalDictMapElem(const DictMapElem *a, const DictMapElem *b) +{ + COMPARE_NODE_FIELD(data); + COMPARE_SCALAR_FIELD(kind); + + return true; +} + +static bool +_equalDictMapExprElem(const DictMapExprElem *a, const DictMapExprElem *b) +{ + COMPARE_NODE_FIELD(left); + COMPARE_NODE_FIELD(right); + COMPARE_SCALAR_FIELD(oper); + + return true; +} + +static bool +_equalDictMapCase(const DictMapCase *a, const DictMapCase *b) +{ + COMPARE_NODE_FIELD(condition); + COMPARE_NODE_FIELD(command); + COMPARE_NODE_FIELD(elsebranch); + COMPARE_SCALAR_FIELD(match); + + return true; +} + +static bool _equalAlterTSDictionaryStmt(const AlterTSDictionaryStmt *a, const AlterTSDictionaryStmt *b) { COMPARE_NODE_FIELD(dictname); @@ -3580,6 +3610,15 @@ equal(const void *a, const void *b) case T_ReassignOwnedStmt: retval = _equalReassignOwnedStmt(a, b); break; + case T_DictMapExprElem: + retval = _equalDictMapExprElem(a, b); + break; + case T_DictMapElem: + retval = _equalDictMapElem(a, b); + break; + case T_DictMapCase: + retval = _equalDictMapCase(a, b); + break; case T_AlterTSDictionaryStmt: retval = _equalAlterTSDictionaryStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd0c26c..37dd2c5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -52,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config_map.h" #include "commands/defrem.h" #include "commands/trigger.h" #include "nodes/makefuncs.h" @@ -241,6 +242,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PartitionSpec *partspec; PartitionBoundSpec *partboundspec; RoleSpec *rolespec; + DictMapElem *dmapelem; MergeWhenClause *mergewhen; } @@ -311,7 +313,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); analyze_option_list analyze_option_elem %type <boolean> opt_or_replace opt_grant_grant_option opt_grant_admin_option - opt_nowait opt_if_exists opt_with_data + opt_nowait opt_if_exists opt_with_data opt_dictionary_map_no %type <ival> opt_nowait_or_skip %type <list> OptRoleList AlterOptRoleList @@ -589,6 +591,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <list> hash_partbound partbound_datum_list range_datum_list %type <defelt> hash_partbound_elem +%type <ival> dictionary_map_set_expr_operator +%type <dmapelem> dictionary_map_dict dictionary_map_command_expr_paren + dictionary_config dictionary_map_case + dictionary_map_action opt_dictionary_map_case_else + dictionary_config_comma + %type <node> merge_when_clause opt_merge_when_and_condition %type <list> merge_when_list @@ -653,13 +661,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JOIN - KEY + KEEP KEY LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MAP MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE @@ -10377,24 +10385,26 @@ AlterTSDictionaryStmt: ; AlterTSConfigurationStmt: - ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list + ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ADD_MAPPING; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = false; n->replace = false; $$ = (Node*)n; } - | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list + | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with dictionary_config { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN; n->cfgname = $5; n->tokentype = $9; - n->dicts = $11; + n->dict_map = $11; + n->dicts = NULL; n->override = true; n->replace = false; $$ = (Node*)n; @@ -10446,6 +10456,100 @@ any_with: WITH {} | WITH_LA {} ; +opt_dictionary_map_no: + NO { $$ = true; } + | { $$ = false; } + ; + +dictionary_config_comma: + dictionary_map_dict { $$ = $1; } + | dictionary_map_dict ',' dictionary_config_comma + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = TSMAP_OP_COMMA; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_action: + KEEP + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_KEEP; + n->data = NULL; + $$ = n; + } + | dictionary_config { $$ = $1; } + ; + +opt_dictionary_map_case_else: + ELSE dictionary_config { $$ = $2; } + | { $$ = NULL; } + ; + +dictionary_map_case: + CASE dictionary_config WHEN opt_dictionary_map_no MATCH THEN dictionary_map_action opt_dictionary_map_case_else END_P + { + DictMapCase *n = makeNode(DictMapCase); + DictMapElem *r = makeNode(DictMapElem); + + n->condition = $2; + n->command = $7; + n->elsebranch = $8; + n->match = !$4; + + r->kind = DICT_MAP_CASE; + r->data = n; + $$ = r; + } + ; + +dictionary_map_set_expr_operator: + UNION { $$ = TSMAP_OP_UNION; } + | EXCEPT { $$ = TSMAP_OP_EXCEPT; } + | INTERSECT { $$ = TSMAP_OP_INTERSECT; } + | MAP { $$ = TSMAP_OP_MAP; } + ; + +dictionary_config: + dictionary_map_command_expr_paren { $$ = $1; } + | dictionary_config dictionary_map_set_expr_operator dictionary_map_command_expr_paren + { + DictMapExprElem *n = makeNode(DictMapExprElem); + DictMapElem *r = makeNode(DictMapElem); + + n->left = $1; + n->oper = $2; + n->right = $3; + + r->kind = DICT_MAP_EXPRESSION; + r->data = n; + $$ = r; + } + ; + +dictionary_map_command_expr_paren: + '(' dictionary_config ')' { $$ = $2; } + | dictionary_map_case { $$ = $1; } + | dictionary_config_comma { $$ = $1; } + ; + +dictionary_map_dict: + any_name + { + DictMapElem *n = makeNode(DictMapElem); + n->kind = DICT_MAP_DICTIONARY; + n->data = $1; + $$ = n; + } + ; /***************************************************************************** * @@ -15254,6 +15358,7 @@ unreserved_keyword: | LOCK_P | LOCKED | LOGGED + | MAP | MAPPING | MATCH | MATCHED @@ -15562,6 +15667,7 @@ reserved_keyword: | INITIALLY | INTERSECT | INTO + | KEEP | LATERAL_P | LEADING | LIMIT diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile index 227468a..e61ad4f 100644 --- a/src/backend/tsearch/Makefile +++ b/src/backend/tsearch/Makefile @@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES)) OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \ dict_simple.o dict_synonym.o dict_thesaurus.o \ dict_ispell.o regis.o spell.o \ - to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o + to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o ts_configmap.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/tsearch/ts_configmap.c b/src/backend/tsearch/ts_configmap.c new file mode 100644 index 0000000..714f2a8 --- /dev/null +++ b/src/backend/tsearch/ts_configmap.c @@ -0,0 +1,1114 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.c + * internal representation of text search configuration and utilities for it + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/tsearch/ts_confimap.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "access/heapam.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/indexing.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_namespace.h" +#include "catalog/namespace.h" +#include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +/* + * Size selected arbitrary, based on assumption that 1024 frames of stack + * is enough for parsing of configurations + */ +#define JSONB_PARSE_STATE_STACK_SIZE 1024 + +/* + * Used during the parsing of TSMapElement from JSONB into internal + * data structures. + */ +typedef enum TSMapParseState +{ + TSMPS_WAIT_ELEMENT, + TSMPS_READ_DICT_OID, + TSMPS_READ_COMPLEX_OBJ, + TSMPS_READ_EXPRESSION, + TSMPS_READ_CASE, + TSMPS_READ_OPERATOR, + TSMPS_READ_COMMAND, + TSMPS_READ_CONDITION, + TSMPS_READ_ELSEBRANCH, + TSMPS_READ_MATCH, + TSMPS_READ_KEEP, + TSMPS_READ_LEFT, + TSMPS_READ_RIGHT +} TSMapParseState; + +/* + * Context used during JSONB parsing to construct a TSMap + */ +typedef struct TSMapJsonbParseData +{ + TSMapParseState states[JSONB_PARSE_STATE_STACK_SIZE]; /* Stack of states of + * JSONB parsing + * automaton */ + int statesIndex; /* Index of current stack frame */ + TSMapElement *element; /* Element that is in construction now */ +} TSMapJsonbParseData; + +static JsonbValue *TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState); +static TSMapElement * JsonbToTSMapElement(JsonbContainer *root); + +/* + * Print name of the namespace into StringInfo variable result + */ +static void +TSMapPrintNamespace(Oid namespaceId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_namespace namespace; + + if (false) + return; + + maprel = heap_open(NamespaceRelationId, AccessShareLock); + mapidx = index_open(NamespaceOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + namespace = (Form_pg_namespace) GETSTRUCT(maptup); + appendStringInfoString(result, namespace->nspname.data); + appendStringInfoChar(result, '.'); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print name of the dictionary into StringInfo variable result + */ +void +TSMapPrintDictName(Oid dictId, StringInfo result) +{ + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + Form_pg_ts_dict dict; + + if (false) + return; +maprel = heap_open(TSDictionaryRelationId, AccessShareLock); + mapidx = index_open(TSDictionaryOidIndexId, AccessShareLock); + + ScanKeyInit(&mapskey, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dictId)); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + maptup = systable_getnext_ordered(mapscan, ForwardScanDirection); + dict = (Form_pg_ts_dict) GETSTRUCT(maptup); + if (!TSDictionaryIsVisible(dictId)) + { + TSMapPrintNamespace(dict->dictnamespace, result); + } + appendStringInfoString(result, dict->dictname.data); + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + heap_close(maprel, AccessShareLock); +} + +/* + * Print the expression into StringInfo variable result + */ +static void +TSMapPrintExpression(TSMapExpression *expression, StringInfo result) +{ + + Assert(expression->left); + if (expression->left->type == TSMAP_EXPRESSION && + expression->left->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, '('); + } + TSMapPrintElement(expression->left, result); + if (expression->left->type == TSMAP_EXPRESSION && + expression->left->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, ')'); + } + + switch (expression->operator) + { + case TSMAP_OP_UNION: + appendStringInfoString(result, " UNION "); + break; + case TSMAP_OP_EXCEPT: + appendStringInfoString(result, " EXCEPT "); + break; + case TSMAP_OP_INTERSECT: + appendStringInfoString(result, " INTERSECT "); + break; + case TSMAP_OP_COMMA: + appendStringInfoString(result, ", "); + break; + case TSMAP_OP_MAP: + appendStringInfoString(result, " MAP "); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } + + Assert(expression->right); + if (expression->right->type == TSMAP_EXPRESSION && + expression->right->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, '('); + } + TSMapPrintElement(expression->right, result); + if (expression->right->type == TSMAP_EXPRESSION && + expression->right->value.objectExpression->operator != expression->operator) + { + appendStringInfoChar(result, ')'); + } +} + +/* + * Print the case configuration construction into StringInfo variable result + */ +static void +TSMapPrintCase(TSMapCase *caseObject, StringInfo result) +{ + appendStringInfoString(result, "CASE "); + + TSMapPrintElement(caseObject->condition, result); + + appendStringInfoString(result, " WHEN "); + if (!caseObject->match) + appendStringInfoString(result, "NO "); + appendStringInfoString(result, "MATCH THEN "); + + TSMapPrintElement(caseObject->command, result); + + if (caseObject->elsebranch != NULL) + { + appendStringInfoString(result, "\nELSE "); + TSMapPrintElement(caseObject->elsebranch, result); + } + appendStringInfoString(result, "\nEND"); +} + +/* + * Print the element into StringInfo result. + * Uses other function and serves for element type detection. + */ +void +TSMapPrintElement(TSMapElement *element, StringInfo result) +{ + switch (element->type) + { + case TSMAP_EXPRESSION: + TSMapPrintExpression(element->value.objectExpression, result); + break; + case TSMAP_DICTIONARY: + TSMapPrintDictName(element->value.objectDictionary, result); + break; + case TSMAP_CASE: + TSMapPrintCase(element->value.objectCase, result); + break; + case TSMAP_KEEP: + appendStringInfoString(result, "KEEP"); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains elements with invalid type."))); + break; + } +} + +/* + * Print the text search configuration as a text. + */ +Datum +dictionary_mapping_to_text(PG_FUNCTION_ARGS) +{ + Oid cfgOid = PG_GETARG_OID(0); + int32 tokentype = PG_GETARG_INT32(1); + StringInfo rawResult; + text *result = NULL; + TSConfigCacheEntry *cacheEntry; + + cacheEntry = lookup_ts_config_cache(cfgOid); + rawResult = makeStringInfo(); + initStringInfo(rawResult); + + if (cacheEntry->lenmap > tokentype && cacheEntry->map[tokentype] != NULL) + { + TSMapElement *element = cacheEntry->map[tokentype]; + + TSMapPrintElement(element, rawResult); + } + + result = cstring_to_text(rawResult->data); + pfree(rawResult); + PG_RETURN_TEXT_P(result); +} + +/* ---------------- + * Functions used to convert TSMap structure into JSONB representation + * ---------------- + */ + +/* + * Convert an integer value into JsonbValue + */ +static JsonbValue * +IntToJsonbValue(int intValue) +{ + char buffer[16]; + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + /* + * String size is based on limit of int capacity up to 12 chars with sign + * and NULL-character + */ + memset(buffer, 0, sizeof(char) * 12); + + pg_ltoa(intValue, buffer); + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(buffer), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1) + )); + return value; +} + +/* + * Convert a FTS configuration expression into JsonbValue + */ +static JsonbValue * +TSMapExpressionToJsonbValue(TSMapExpression *expression, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("operator"); + key.val.string.val = "operator"; + value = IntToJsonbValue(expression->operator); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("left"); + key.val.string.val = "left"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->left, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("right"); + key.val.string.val = "right"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(expression->right, jsonbState); + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS configuration case into JsonbValue + */ +static JsonbValue * +TSMapCaseToJsonbValue(TSMapCase *caseObject, JsonbParseState *jsonbState) +{ + JsonbValue key; + JsonbValue *value = NULL; + + pushJsonbValue(&jsonbState, WJB_BEGIN_OBJECT, NULL); + + key.type = jbvString; + key.val.string.len = strlen("condition"); + key.val.string.val = "condition"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->condition, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + key.type = jbvString; + key.val.string.len = strlen("command"); + key.val.string.val = "command"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->command, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + if (caseObject->elsebranch != NULL) + { + key.type = jbvString; + key.val.string.len = strlen("elsebranch"); + key.val.string.val = "elsebranch"; + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + value = TSMapElementToJsonbValue(caseObject->elsebranch, jsonbState); + + if (value && IsAJsonbScalar(value)) + pushJsonbValue(&jsonbState, WJB_VALUE, value); + } + + key.type = jbvString; + key.val.string.len = strlen("match"); + key.val.string.val = "match"; + + value = IntToJsonbValue(caseObject->match ? 1 : 0); + + pushJsonbValue(&jsonbState, WJB_KEY, &key); + pushJsonbValue(&jsonbState, WJB_VALUE, value); + + return pushJsonbValue(&jsonbState, WJB_END_OBJECT, NULL); +} + +/* + * Convert a FTS KEEP command into JsonbValue + */ +static JsonbValue * +TSMapKeepToJsonbValue(JsonbParseState *jsonbState) +{ + JsonbValue *value = palloc0(sizeof(JsonbValue)); + + value->type = jbvString; + value->val.string.len = strlen("keep"); + value->val.string.val = "keep"; + + return pushJsonbValue(&jsonbState, WJB_VALUE, value); +} + +/* + * Convert a FTS element into JsonbValue. Common point for all types of TSMapElement + */ +JsonbValue * +TSMapElementToJsonbValue(TSMapElement *element, JsonbParseState *jsonbState) +{ + JsonbValue *result = NULL; + + if (element != NULL) + { + switch (element->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionToJsonbValue(element->value.objectExpression, jsonbState); + break; + case TSMAP_DICTIONARY: + result = IntToJsonbValue(element->value.objectDictionary); + break; + case TSMAP_CASE: + result = TSMapCaseToJsonbValue(element->value.objectCase, jsonbState); + break; + case TSMAP_KEEP: + result = TSMapKeepToJsonbValue(jsonbState); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Required text search configuration contains elements with invalid type."))); + break; + } + } + return result; +} + +/* + * Convert a FTS configuration into JSONB + */ +Jsonb * +TSMapToJsonb(TSMapElement *element) +{ + JsonbParseState *jsonbState = NULL; + JsonbValue *out; + Jsonb *result; + + out = TSMapElementToJsonbValue(element, jsonbState); + + result = JsonbValueToJsonb(out); + return result; +} + +/* ---------------- + * Functions used to get TSMap structure from JSONB representation + * ---------------- + */ + +/* + * Extract an integer from JsonbValue + */ +static int +JsonbValueToInt(JsonbValue *value) +{ + char *str; + + str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(value->val.numeric))); + return pg_atoi(str, sizeof(int), 0); +} + +/* + * Check is a key one of FTS configuration case fields + */ +static bool +IsTSMapCaseKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "match") == 0 || strcmp(key, "condition") == 0 || strcmp(key, "command") == 0 || strcmp(key, "elsebranch") == 0; +} + +/* + * Check is a key one of FTS configuration expression fields + */ +static bool +IsTSMapExpressionKey(JsonbValue *value) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value->val.string.len + 1)); + + key[value->val.string.len] = '\0'; + memcpy(key, value->val.string.val, sizeof(char) * value->val.string.len); + return strcmp(key, "operator") == 0 || strcmp(key, "left") == 0 || strcmp(key, "right") == 0; +} + +/* + * Configure parseData->element according to value (key) + */ +static void +JsonbBeginObjectKey(JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *parentElement = parseData->element; + + parseData->element = palloc0(sizeof(TSMapElement)); + parseData->element->parent = parentElement; + + /* Overwrite object-type state based on key */ + if (IsTSMapExpressionKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_EXPRESSION; + parseData->element->type = TSMAP_EXPRESSION; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapExpression)); + } + else if (IsTSMapCaseKey(&value)) + { + parseData->states[parseData->statesIndex] = TSMPS_READ_CASE; + parseData->element->type = TSMAP_CASE; + parseData->element->value.objectExpression = palloc0(sizeof(TSMapCase)); + } +} + +/* + * Process a JsonbValue inside a FTS configuration expression + */ +static void +JsonbKeyExpressionProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "operator") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_OPERATOR; + else if (strcmp(key, "left") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_LEFT; + else if (strcmp(key, "right") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_RIGHT; +} + +/* + * Process a JsonbValue inside a FTS configuration case + */ +static void +JsonbKeyCaseProcessing(JsonbValue value, TSMapJsonbParseData *parseData) +{ + /* + * JsonbValue string may be not null-terminated. Convert it for appropriate + * behavior of strcmp function. + */ + char *key = palloc0(sizeof(char) * (value.val.string.len + 1)); + + memcpy(key, value.val.string.val, sizeof(char) * value.val.string.len); + parseData->statesIndex++; + + if (parseData->statesIndex >= JSONB_PARSE_STATE_STACK_SIZE) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("configuration is too complex to be parsed"), + errdetail("Configurations with more than %d nested objected are not supported.", + JSONB_PARSE_STATE_STACK_SIZE))); + + if (strcmp(key, "condition") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_CONDITION; + else if (strcmp(key, "command") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_COMMAND; + else if (strcmp(key, "elsebranch") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_ELSEBRANCH; + else if (strcmp(key, "match") == 0) + parseData->states[parseData->statesIndex] = TSMPS_READ_MATCH; +} + +/* + * Convert a JsonbValue into OID TSMapElement + */ +static TSMapElement * +JsonbValueToOidElement(JsonbValue *value, TSMapElement *parent) +{ + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + element->type = TSMAP_DICTIONARY; + element->value.objectDictionary = JsonbValueToInt(value); + return element; +} + +/* + * Convert a JsonbValue into string TSMapElement. + * Used for special values such as KEEP command + */ +static TSMapElement * +JsonbValueReadString(JsonbValue *value, TSMapElement *parent) +{ + char *str; + TSMapElement *element = palloc0(sizeof(TSMapElement)); + + element->parent = parent; + str = palloc0(sizeof(char) * (value->val.string.len + 1)); + memcpy(str, value->val.string.val, sizeof(char) * value->val.string.len); + + if (strcmp(str, "keep") == 0) + element->type = TSMAP_KEEP; + + pfree(str); + + return element; +} + +/* + * Process a JsonbValue object + */ +static void +JsonbProcessElement(JsonbIteratorToken r, JsonbValue value, TSMapJsonbParseData *parseData) +{ + TSMapElement *element = NULL; + + switch (r) + { + case WJB_KEY: + + /* + * Construct an TSMapElement object. At first key inside JSONB + * object a type is selected based on key. + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMPLEX_OBJ) + JsonbBeginObjectKey(value, parseData); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_EXPRESSION) + JsonbKeyExpressionProcessing(value, parseData); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CASE) + JsonbKeyCaseProcessing(value, parseData); + + break; + case WJB_BEGIN_OBJECT: + + /* + * Begin construction of new object + */ + parseData->statesIndex++; + parseData->states[parseData->statesIndex] = TSMPS_READ_COMPLEX_OBJ; + break; + case WJB_END_OBJECT: + + /* + * Save constructed object based on current state of parser + */ + if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->parent->value.objectExpression->left = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->parent->value.objectExpression->right = parseData->element; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->parent->value.objectCase->condition = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->parent->value.objectCase->command = parseData->element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->parent->value.objectCase->elsebranch = parseData->element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_VALUE: + + /* + * Save a value inside constructing object + */ + if (value.type == jbvBinary) + element = JsonbToTSMapElement(value.val.binary.data); + else if (value.type == jbvString) + element = JsonbValueReadString(&value, parseData->element); + else if (value.type == jbvNumeric) + element = JsonbValueToOidElement(&value, parseData->element); + else + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + + if (parseData->states[parseData->statesIndex] == TSMPS_READ_CONDITION) + parseData->element->value.objectCase->condition = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_COMMAND) + parseData->element->value.objectCase->command = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_ELSEBRANCH) + parseData->element->value.objectCase->elsebranch = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_MATCH) + parseData->element->value.objectCase->match = JsonbValueToInt(&value) == 1 ? true : false; + + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_OPERATOR) + parseData->element->value.objectExpression->operator = JsonbValueToInt(&value); + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_LEFT) + parseData->element->value.objectExpression->left = element; + else if (parseData->states[parseData->statesIndex] == TSMPS_READ_RIGHT) + parseData->element->value.objectExpression->right = element; + + parseData->statesIndex--; + Assert(parseData->statesIndex >= 0); + if (parseData->element->parent != NULL) + parseData->element = parseData->element->parent; + break; + case WJB_ELEM: + + /* + * Store a simple element such as dictionary OID + */ + if (parseData->states[parseData->statesIndex] == TSMPS_WAIT_ELEMENT) + { + if (parseData->element != NULL) + parseData->element = JsonbValueToOidElement(&value, parseData->element->parent); + else + parseData->element = JsonbValueToOidElement(&value, NULL); + } + break; + default: + /* Ignore unused JSONB tokens */ + break; + } +} + +/* + * Convert a JsonbContainer into TSMapElement + */ +static TSMapElement * +JsonbToTSMapElement(JsonbContainer *root) +{ + TSMapJsonbParseData parseData; + JsonbIteratorToken r; + JsonbIterator *it; + JsonbValue val; + + parseData.statesIndex = 0; + parseData.states[parseData.statesIndex] = TSMPS_WAIT_ELEMENT; + parseData.element = NULL; + + it = JsonbIteratorInit(root); + + while ((r = JsonbIteratorNext(&it, &val, true)) != WJB_DONE) + JsonbProcessElement(r, val, &parseData); + + return parseData.element; +} + +/* + * Convert a JSONB into TSMapElement + */ +TSMapElement * +JsonbToTSMap(Jsonb *json) +{ + JsonbContainer *root = &json->root; + + return JsonbToTSMapElement(root); +} + +/* ---------------- + * Text Search Configuration Map Utils + * ---------------- + */ + +/* + * Dynamically extendable list of OIDs + */ +typedef struct OidList +{ + Oid *data; + int size; /* Size of data array. Uninitialized elements + * in data filled with InvalidOid */ +} OidList; + +/* + * Initialize a list + */ +static OidList * +OidListInit() +{ + OidList *result = palloc0(sizeof(OidList)); + + result->size = 1; + result->data = palloc0(result->size * sizeof(Oid)); + result->data[0] = InvalidOid; + return result; +} + +/* + * Add a new OID into list. If it is already stored in list, it won't be add second time. + */ +static void +OidListAdd(OidList *list, Oid oid) +{ + int i; + + /* Search for the Oid in the list */ + for (i = 0; list->data[i] != InvalidOid; i++) + if (list->data[i] == oid) + return; + + /* If not found, insert it in the end of the list */ + if (i >= list->size - 1) + { + int j; + + list->size = list->size * 2; + list->data = repalloc(list->data, sizeof(Oid) * list->size); + + for (j = i; j < list->size; j++) + list->data[j] = InvalidOid; + } + list->data[i] = oid; +} + +/* + * Get OIDs of all dictionaries used in TSMapElement. + * Used for internal recursive calls. + */ +static void +TSMapGetDictionariesInternal(TSMapElement *config, OidList *list) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapGetDictionariesInternal(config->value.objectExpression->left, list); + TSMapGetDictionariesInternal(config->value.objectExpression->right, list); + break; + case TSMAP_CASE: + TSMapGetDictionariesInternal(config->value.objectCase->command, list); + TSMapGetDictionariesInternal(config->value.objectCase->condition, list); + if (config->value.objectCase->elsebranch != NULL) + TSMapGetDictionariesInternal(config->value.objectCase->elsebranch, list); + break; + case TSMAP_DICTIONARY: + OidListAdd(list, config->value.objectDictionary); + break; + } +} + +/* + * Get OIDs of all dictionaries used in TSMapElement + */ +Oid * +TSMapGetDictionaries(TSMapElement *config) +{ + Oid *result; + OidList *list = OidListInit(); + + TSMapGetDictionariesInternal(config, list); + + result = list->data; + pfree(list); + + return result; +} + +/* + * Replace one dictionary OID with another in all instances inside a configuration + */ +void +TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict) +{ + switch (config->type) + { + case TSMAP_EXPRESSION: + TSMapReplaceDictionary(config->value.objectExpression->left, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectExpression->right, oldDict, newDict); + break; + case TSMAP_CASE: + TSMapReplaceDictionary(config->value.objectCase->command, oldDict, newDict); + TSMapReplaceDictionary(config->value.objectCase->condition, oldDict, newDict); + if (config->value.objectCase->elsebranch != NULL) + TSMapReplaceDictionary(config->value.objectCase->elsebranch, oldDict, newDict); + break; + case TSMAP_DICTIONARY: + if (config->value.objectDictionary == oldDict) + config->value.objectDictionary = newDict; + break; + } +} + +/* ---------------- + * Text Search Configuration Map Memory Management + * ---------------- + */ + +/* + * Move a FTS configuration expression to another memory context + */ +static TSMapElement * +TSMapExpressionMoveToMemoryContext(TSMapExpression *expression, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapExpression *resultExpression = MemoryContextAlloc(context, sizeof(TSMapExpression)); + + memset(resultExpression, 0, sizeof(TSMapExpression)); + result->value.objectExpression = resultExpression; + result->type = TSMAP_EXPRESSION; + + resultExpression->operator = expression->operator; + + resultExpression->left = TSMapMoveToMemoryContext(expression->left, context); + resultExpression->left->parent = result; + + resultExpression->right = TSMapMoveToMemoryContext(expression->right, context); + resultExpression->right->parent = result; + + return result; +} + +/* + * Move a FTS configuration case to another memory context + */ +static TSMapElement * +TSMapCaseMoveToMemoryContext(TSMapCase *caseObject, MemoryContext context) +{ + TSMapElement *result = MemoryContextAlloc(context, sizeof(TSMapElement)); + TSMapCase *resultCaseObject = MemoryContextAlloc(context, sizeof(TSMapCase)); + + memset(resultCaseObject, 0, sizeof(TSMapCase)); + result->value.objectCase = resultCaseObject; + result->type = TSMAP_CASE; + + resultCaseObject->match = caseObject->match; + + resultCaseObject->command = TSMapMoveToMemoryContext(caseObject->command, context); + resultCaseObject->command->parent = result; + + resultCaseObject->condition = TSMapMoveToMemoryContext(caseObject->condition, context); + resultCaseObject->condition->parent = result; + + if (caseObject->elsebranch != NULL) + { + resultCaseObject->elsebranch = TSMapMoveToMemoryContext(caseObject->elsebranch, context); + resultCaseObject->elsebranch->parent = result; + } + + return result; +} + +/* + * Move a FTS configuration to another memory context + */ +TSMapElement * +TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context) +{ + TSMapElement *result = NULL; + + switch (config->type) + { + case TSMAP_EXPRESSION: + result = TSMapExpressionMoveToMemoryContext(config->value.objectExpression, context); + break; + case TSMAP_CASE: + result = TSMapCaseMoveToMemoryContext(config->value.objectCase, context); + break; + case TSMAP_DICTIONARY: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_DICTIONARY; + result->value.objectDictionary = config->value.objectDictionary; + break; + case TSMAP_KEEP: + result = MemoryContextAlloc(context, sizeof(TSMapElement)); + result->type = TSMAP_KEEP; + result->value.object = NULL; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains object with invalid type."))); + break; + } + + return result; +} + +/* + * Free memory occupied by FTS configuration expression + */ +static void +TSMapExpressionFree(TSMapExpression *expression) +{ + if (expression->left) + TSMapElementFree(expression->left); + if (expression->right) + TSMapElementFree(expression->right); + pfree(expression); +} + +/* + * Free memory occupied by FTS configuration case + */ +static void +TSMapCaseFree(TSMapCase *caseObject) +{ + TSMapElementFree(caseObject->condition); + TSMapElementFree(caseObject->command); + TSMapElementFree(caseObject->elsebranch); + pfree(caseObject); +} + +/* + * Free memory occupied by FTS configuration element + */ +void +TSMapElementFree(TSMapElement *element) +{ + if (element != NULL) + { + switch (element->type) + { + case TSMAP_CASE: + TSMapCaseFree(element->value.objectCase); + break; + case TSMAP_EXPRESSION: + TSMapExpressionFree(element->value.objectExpression); + break; + } + pfree(element); + } +} + +/* + * Do a deep comparison of two TSMapElements. Doesn't check parents of elements + */ +bool +TSMapElementEquals(TSMapElement *a, TSMapElement *b) +{ + bool result = true; + + if (a->type == b->type) + { + switch (a->type) + { + case TSMAP_CASE: + if (!TSMapElementEquals(a->value.objectCase->condition, b->value.objectCase->condition)) + result = false; + if (!TSMapElementEquals(a->value.objectCase->command, b->value.objectCase->command)) + result = false; + + if (a->value.objectCase->elsebranch != NULL && b->value.objectCase->elsebranch != NULL) + { + if (!TSMapElementEquals(a->value.objectCase->elsebranch, b->value.objectCase->elsebranch)) + result = false; + } + else if (a->value.objectCase->elsebranch != NULL || b->value.objectCase->elsebranch != NULL) + result = false; + + if (a->value.objectCase->match != b->value.objectCase->match) + result = false; + break; + case TSMAP_EXPRESSION: + if (!TSMapElementEquals(a->value.objectExpression->left, b->value.objectExpression->left)) + result = false; + if (!TSMapElementEquals(a->value.objectExpression->right, b->value.objectExpression->right)) + result = false; + if (a->value.objectExpression->operator != b->value.objectExpression->operator) + result = false; + break; + case TSMAP_DICTIONARY: + result = a->value.objectDictionary == b->value.objectDictionary; + break; + case TSMAP_KEEP: + result = true; + } + } + else + result = false; + + return result; +} diff --git a/src/backend/tsearch/ts_parse.c b/src/backend/tsearch/ts_parse.c index 7b69ef5..f476abb 100644 --- a/src/backend/tsearch/ts_parse.c +++ b/src/backend/tsearch/ts_parse.c @@ -16,58 +16,157 @@ #include "tsearch/ts_cache.h" #include "tsearch/ts_utils.h" +#include "tsearch/ts_configmap.h" +#include "utils/builtins.h" +#include "funcapi.h" #define IGNORE_LONGLEXEME 1 -/* +/*------------------- * Lexize subsystem + *------------------- */ +/* + * Representation of token produced by FTS parser. It contains intermediate + * lexemes in case of phrase dictionary processing. + */ typedef struct ParsedLex { - int type; - char *lemm; - int lenlemm; - struct ParsedLex *next; + int type; /* Token type */ + char *lemm; /* Token itself */ + int lenlemm; /* Length of the token string */ + int maplen; /* Length of the map */ + bool *accepted; /* Is accepted by some dictionary */ + bool *rejected; /* Is rejected by all dictionaries */ + bool *notFinished; /* Some dictionary not finished processing and + * waits for more tokens */ + struct ParsedLex *next; /* Next token in the list */ + TSMapElement *relatedRule; /* Rule which is used to produce lexemes from + * the token */ } ParsedLex; +/* + * List of tokens produced by FTS parser. + */ typedef struct ListParsedLex { ParsedLex *head; ParsedLex *tail; } ListParsedLex; -typedef struct +/* + * Dictionary state shared between processing of different tokens + */ +typedef struct DictState { - TSConfigCacheEntry *cfg; - Oid curDictId; - int posDict; - DictSubState dictState; - ParsedLex *curSub; - ListParsedLex towork; /* current list to work */ - ListParsedLex waste; /* list of lexemes that already lexized */ + Oid relatedDictionary; /* DictState contains state of dictionary + * with this Oid */ + DictSubState subState; /* Internal state of the dictionary used to + * store some state between dictionary calls */ + ListParsedLex acceptedTokens; /* Tokens which are processed and + * accepted, used in last returned result + * by the dictionary */ + ListParsedLex intermediateTokens; /* Tokens which are not accepted, but + * were processed by thesaurus-like + * dictionary */ + bool storeToAccepted; /* Should current token be appended to + * accepted or intermediate tokens */ + bool processed; /* Is the dictionary take control during + * current token processing */ + TSLexeme *tmpResult; /* Last result returned by thesaurus-like + * dictionary, if dictionary still waiting for + * more lexemes */ +} DictState; - /* - * fields to store last variant to lexize (basically, thesaurus or similar - * to, which wants several lexemes - */ +/* + * List of dictionary states + */ +typedef struct DictStateList +{ + int listLength; + DictState *states; +} DictStateList; - ParsedLex *lastRes; - TSLexeme *tmpRes; +/* + * Buffer entry with lexemes produced from current token + */ +typedef struct LexemesBufferEntry +{ + TSMapElement *key; /* Element of the mapping configuration produced the entry */ + ParsedLex *token; /* Token used for production of the lexemes */ + TSLexeme *data; /* Lexemes produced from current token */ +} LexemesBufferEntry; + +/* + * Buffer with lexemes produced from current token + */ +typedef struct LexemesBuffer +{ + int size; + LexemesBufferEntry *data; +} LexemesBuffer; + +/* + * Storage for accepted and possible accepted lexemes + */ +typedef struct ResultStorage +{ + TSLexeme *lexemes; /* Processed lexemes, which is not yet + * accepted */ + TSLexeme *accepted; /* Already accepted lexemes */ +} ResultStorage; + +/* + * FTS processing context + */ +typedef struct LexizeData +{ + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + DictStateList dslist; /* List of all currently stored states of + * dictionaries */ + ListParsedLex towork; /* Current list to work */ + ListParsedLex waste; /* List of lexemes that already lexized */ + LexemesBuffer buffer; /* Buffer of processed lexemes. Used to avoid + * multiple execution of token lexize process + * with same parameters */ + ResultStorage delayedResults; /* Results that should be returned but may + * be rejected in future */ + Oid skipDictionary; /* The dictionary we should skip during + * processing. Used to avoid infinite loop in + * configuration with phrase dictionary */ + bool debugContext; /* If true, relatedRule attribute is filled */ } LexizeData; -static void -LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +/* + * FTS processing debug context. Used during ts_debug calls. + */ +typedef struct TSDebugContext { - ld->cfg = cfg; - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->towork.head = ld->towork.tail = ld->curSub = NULL; - ld->waste.head = ld->waste.tail = NULL; - ld->lastRes = NULL; - ld->tmpRes = NULL; -} + TSConfigCacheEntry *cfg; /* Text search configuration mappings for + * current configuration */ + TSParserCacheEntry *prsobj; /* Parser context of current ts_debug context */ + LexDescr *tokenTypes; /* Token types supported by current parser */ + void *prsdata; /* Parser data of current ts_debug context */ + LexizeData ldata; /* Lexize data of current ts_debug context */ + int tokentype; /* Last token tokentype */ + TSLexeme *savedLexemes; /* Last token lexemes stored for ts_debug + * output */ + ParsedLex *leftTokens; /* Corresponded ParsedLex */ +} TSDebugContext; + +static TSLexeme *TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression); +static TSLexeme *LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config); + +/*------------------- + * ListParsedLex API + *------------------- + */ +/* + * Add a ParsedLex to the end of the list + */ static void LPLAddTail(ListParsedLex *list, ParsedLex *newpl) { @@ -81,274 +180,1291 @@ LPLAddTail(ListParsedLex *list, ParsedLex *newpl) newpl->next = NULL; } -static ParsedLex * -LPLRemoveHead(ListParsedLex *list) -{ - ParsedLex *res = list->head; +/* + * Add a copy of ParsedLex to the end of the list + */ +static void +LPLAddTailCopy(ListParsedLex *list, ParsedLex *newpl) +{ + ParsedLex *copy = palloc0(sizeof(ParsedLex)); + + copy->lenlemm = newpl->lenlemm; + copy->type = newpl->type; + copy->lemm = newpl->lemm; + copy->relatedRule = newpl->relatedRule; + copy->next = NULL; + + if (list->tail) + { + list->tail->next = copy; + list->tail = copy; + } + else + list->head = list->tail = copy; +} + +/* + * Remove the head of the list. Return pointer to detached head + */ +static ParsedLex * +LPLRemoveHead(ListParsedLex *list) +{ + ParsedLex *res = list->head; + + if (list->head) + list->head = list->head->next; + + if (list->head == NULL) + list->tail = NULL; + + return res; +} + +/* + * Remove all ParsedLex from the list + */ +static void +LPLClear(ListParsedLex *list) +{ + ParsedLex *tmp, + *ptr = list->head; + + while (ptr) + { + tmp = ptr->next; + pfree(ptr); + ptr = tmp; + } + + list->head = list->tail = NULL; +} + +/*------------------- + * LexizeData manipulation functions + *------------------- + */ + +/* + * Initialize empty LexizeData object + */ +static void +LexizeInit(LexizeData *ld, TSConfigCacheEntry *cfg) +{ + ld->cfg = cfg; + ld->skipDictionary = InvalidOid; + ld->towork.head = ld->towork.tail = NULL; + ld->waste.head = ld->waste.tail = NULL; + ld->dslist.listLength = 0; + ld->dslist.states = NULL; + ld->buffer.size = 0; + ld->buffer.data = NULL; + ld->delayedResults.lexemes = NULL; + ld->delayedResults.accepted = NULL; +} + +/* + * Add a token to the processing queue + */ +static void +LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) +{ + ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + + newpl->type = type; + newpl->lemm = lemm; + newpl->lenlemm = lenlemm; + newpl->relatedRule = NULL; + LPLAddTail(&ld->towork, newpl); +} + +/* + * Remove head of the processing queue + */ +static void +RemoveHead(LexizeData *ld) +{ + LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); +} + +/* + * Set token corresponded to current lexeme + */ +static void +setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +{ + if (correspondLexem) + *correspondLexem = ld->waste.head; + else + LPLClear(&ld->waste); + + ld->waste.head = ld->waste.tail = NULL; +} + +/*------------------- + * DictState manipulation functions + *------------------- + */ + +/* + * Get a state of dictionary based on its OID + */ +static DictState * +DictStateListGet(DictStateList *list, Oid dictId) +{ + int i; + DictState *result = NULL; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + result = &list->states[i]; + + return result; +} + +/* + * Remove a state of dictionary based on its OID + */ +static void +DictStateListRemove(DictStateList *list, Oid dictId) +{ + int i; + + for (i = 0; i < list->listLength; i++) + if (list->states[i].relatedDictionary == dictId) + break; + + if (i != list->listLength) + { + memcpy(list->states + i, list->states + i + 1, sizeof(DictState) * (list->listLength - i - 1)); + list->listLength--; + if (list->listLength == 0) + list->states = NULL; + else + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + } +} + +/* + * Insert a state of dictionary with specified OID + */ +static DictState * +DictStateListAdd(DictStateList *list, DictState *state) +{ + DictStateListRemove(list, state->relatedDictionary); + + list->listLength++; + if (list->states) + list->states = repalloc(list->states, sizeof(DictState) * list->listLength); + else + list->states = palloc0(sizeof(DictState) * list->listLength); + + memcpy(list->states + list->listLength - 1, state, sizeof(DictState)); + + return list->states + list->listLength - 1; +} + +/* + * Remove states of all dictionaries + */ +static void +DictStateListClear(DictStateList *list) +{ + list->listLength = 0; + if (list->states) + pfree(list->states); + list->states = NULL; +} + +/*------------------- + * LexemesBuffer manipulation functions + *------------------- + */ + +/* + * Check if there is a saved lexeme generated by specified TSMapElement + */ +static bool +LexemesBufferContains(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + return true; + + return false; +} + +/* + * Get a saved lexeme generated by specified TSMapElement + */ +static TSLexeme * +LexemesBufferGet(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + TSLexeme *result = NULL; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + result = buffer->data[i].data; + + return result; +} + +/* + * Remove a saved lexeme generated by specified TSMapElement + */ +static void +LexemesBufferRemove(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token) +{ + int i; + + for (i = 0; i < buffer->size; i++) + if (TSMapElementEquals(buffer->data[i].key, key) && buffer->data[i].token == token) + break; + + if (i != buffer->size) + { + memcpy(buffer->data + i, buffer->data + i + 1, sizeof(LexemesBufferEntry) * (buffer->size - i - 1)); + buffer->size--; + if (buffer->size == 0) + buffer->data = NULL; + else + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + } +} + +/* + * Same a lexeme generated by specified TSMapElement + */ +static void +LexemesBufferAdd(LexemesBuffer *buffer, TSMapElement *key, ParsedLex *token, TSLexeme *data) +{ + LexemesBufferRemove(buffer, key, token); + + buffer->size++; + if (buffer->data) + buffer->data = repalloc(buffer->data, sizeof(LexemesBufferEntry) * buffer->size); + else + buffer->data = palloc0(sizeof(LexemesBufferEntry) * buffer->size); + + buffer->data[buffer->size - 1].token = token; + buffer->data[buffer->size - 1].key = key; + buffer->data[buffer->size - 1].data = data; +} + +/* + * Remove all lexemes saved in a buffer + */ +static void +LexemesBufferClear(LexemesBuffer *buffer) +{ + int i; + bool *skipEntry = palloc0(sizeof(bool) * buffer->size); + + for (i = 0; i < buffer->size; i++) + { + if (buffer->data[i].data != NULL && !skipEntry[i]) + { + int j; + + for (j = 0; j < buffer->size; j++) + if (buffer->data[i].data == buffer->data[j].data) + skipEntry[j] = true; + + pfree(buffer->data[i].data); + } + } + + buffer->size = 0; + if (buffer->data) + pfree(buffer->data); + buffer->data = NULL; +} + +/*------------------- + * TSLexeme util functions + *------------------- + */ + +/* + * Get size of TSLexeme except empty-lexeme + */ +static int +TSLexemeGetSize(TSLexeme *lex) +{ + int result = 0; + TSLexeme *ptr = lex; + + while (ptr && ptr->lexeme) + { + result++; + ptr++; + } + + return result; +} + +/* + * Remove repeated lexemes. Also remove copies of whole nvariant groups. + */ +static TSLexeme * +TSLexemeRemoveDuplications(TSLexeme *lexeme) +{ + TSLexeme *res; + int curLexIndex; + int i; + int lexemeSize = TSLexemeGetSize(lexeme); + int shouldCopyCount = lexemeSize; + bool *shouldCopy; + + if (lexeme == NULL) + return NULL; + + shouldCopy = palloc(sizeof(bool) * lexemeSize); + memset(shouldCopy, true, sizeof(bool) * lexemeSize); + + for (curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + for (i = curLexIndex + 1; i < lexemeSize; i++) + { + if (!shouldCopy[i]) + continue; + + if (strcmp(lexeme[curLexIndex].lexeme, lexeme[i].lexeme) == 0) + { + if (lexeme[curLexIndex].nvariant == lexeme[i].nvariant) + { + shouldCopy[i] = false; + shouldCopyCount--; + continue; + } + else + { + /* + * Check for same set of lexemes in another nvariant + * series + */ + int nvariantCountL = 0; + int nvariantCountR = 0; + int nvariantOverlap = 1; + int j; + + for (j = 0; j < lexemeSize; j++) + if (lexeme[curLexIndex].nvariant == lexeme[j].nvariant) + nvariantCountL++; + for (j = 0; j < lexemeSize; j++) + if (lexeme[i].nvariant == lexeme[j].nvariant) + nvariantCountR++; + + if (nvariantCountL != nvariantCountR) + continue; + + for (j = 1; j < nvariantCountR; j++) + { + if (strcmp(lexeme[curLexIndex + j].lexeme, lexeme[i + j].lexeme) == 0 + && lexeme[curLexIndex + j].nvariant == lexeme[i + j].nvariant) + nvariantOverlap++; + } + + if (nvariantOverlap != nvariantCountR) + continue; + + for (j = 0; j < nvariantCountR; j++) + shouldCopy[i + j] = false; + } + } + } + } + + res = palloc0(sizeof(TSLexeme) * (shouldCopyCount + 1)); + + for (i = 0, curLexIndex = 0; curLexIndex < lexemeSize; curLexIndex++) + { + if (shouldCopy[curLexIndex]) + { + memcpy(res + i, lexeme + curLexIndex, sizeof(TSLexeme)); + i++; + } + } + + pfree(shouldCopy); + return res; +} + +/* + * Combine two lexeme lists with respect to positions + */ +static TSLexeme * +TSLexemeMergePositions(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + + if (left != NULL || right != NULL) + { + int left_i = 0; + int right_i = 0; + int left_max_nvariant = 0; + int i; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + for (i = 0; i < right_size; i++) + right[i].nvariant += left_max_nvariant; + if (right && right[0].flags & TSL_ADDPOS) + right[0].flags &= ~TSL_ADDPOS; + + i = 0; + while (i < left_size + right_size) + { + if (left_i < left_size) + { + do + { + result[i++] = left[left_i++]; + } while (left && left[left_i].lexeme && (left[left_i].flags & TSL_ADDPOS) == 0); + } + + if (right_i < right_size) + { + do + { + result[i++] = right[right_i++]; + } while (right && right[right_i].lexeme && (right[right_i].flags & TSL_ADDPOS) == 0); + } + } + } + return result; +} + +/* + * Split lexemes generated by regular dictionaries and multi-input dictionaries + * and combine them with respect to positions + */ +static TSLexeme * +TSLexemeFilterMulti(TSLexeme *lexemes) +{ + TSLexeme *result; + TSLexeme *ptr = lexemes; + int multi_lexemes = 0; + + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + multi_lexemes++; + ptr++; + } + + if (multi_lexemes > 0) + { + TSLexeme *lexemes_multi = palloc0(sizeof(TSLexeme) * (multi_lexemes + 1)); + TSLexeme *lexemes_rest = palloc0(sizeof(TSLexeme) * (TSLexemeGetSize(lexemes) - multi_lexemes + 1)); + int rest_i = 0; + int multi_i = 0; + + ptr = lexemes; + while (ptr && ptr->lexeme) + { + if (ptr->flags & TSL_MULTI) + lexemes_multi[multi_i++] = *ptr; + else + lexemes_rest[rest_i++] = *ptr; + + ptr++; + } + result = TSLexemeMergePositions(lexemes_rest, lexemes_multi); + } + else + { + result = TSLexemeMergePositions(lexemes, NULL); + } + + return result; +} + +/* + * Mark lexemes as generated by multi-input (thesaurus-like) dictionary + */ +static void +TSLexemeMarkMulti(TSLexeme *lexemes) +{ + TSLexeme *ptr = lexemes; + + while (ptr && ptr->lexeme) + { + ptr->flags |= TSL_MULTI; + ptr++; + } +} + +/*------------------- + * Lexemes set operations + *------------------- + */ + +/* + * Combine left and right lexeme lists into one. + * If append is true, right lexemes added after last left lexeme with TSL_ADDPOS flag + */ +static TSLexeme * +TSLexemeUnionOpt(TSLexeme *left, TSLexeme *right, bool append) +{ + TSLexeme *result; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + int left_max_nvariant = 0; + int i; + + if (left == NULL && right == NULL) + { + result = NULL; + } + else + { + result = palloc0(sizeof(TSLexeme) * (left_size + right_size + 1)); + + for (i = 0; i < left_size; i++) + if (left[i].nvariant > left_max_nvariant) + left_max_nvariant = left[i].nvariant; + + if (left_size > 0) + memcpy(result, left, sizeof(TSLexeme) * left_size); + if (right_size > 0) + memcpy(result + left_size, right, sizeof(TSLexeme) * right_size); + if (append && left_size > 0 && right_size > 0) + result[left_size].flags |= TSL_ADDPOS; + + for (i = left_size; i < left_size + right_size; i++) + result[i].nvariant += left_max_nvariant; + } + + return result; +} + +/* + * Combine left and right lexeme lists into one + */ +static TSLexeme * +TSLexemeUnion(TSLexeme *left, TSLexeme *right) +{ + return TSLexemeUnionOpt(left, right, false); +} + +/* + * Remove common lexemes and return only which is stored in left list + */ +static TSLexeme * +TSLexemeExcept(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (!found) + result[k++] = left[i]; + } + + return result; +} + +/* + * Keep only common lexemes + */ +static TSLexeme * +TSLexemeIntersect(TSLexeme *left, TSLexeme *right) +{ + TSLexeme *result = NULL; + int i, + j, + k; + int left_size = TSLexemeGetSize(left); + int right_size = TSLexemeGetSize(right); + + result = palloc0(sizeof(TSLexeme) * (left_size + 1)); + + for (k = 0, i = 0; i < left_size; i++) + { + bool found = false; + + for (j = 0; j < right_size; j++) + if (strcmp(left[i].lexeme, right[j].lexeme) == 0) + found = true; + + if (found) + result[k++] = left[i]; + } + + return result; +} + +/*------------------- + * Result storage functions + *------------------- + */ + +/* + * Add a lexeme to the result storage + */ +static void +ResultStorageAdd(ResultStorage *storage, ParsedLex *token, TSLexeme *lexs) +{ + TSLexeme *oldLexs = storage->lexemes; + + storage->lexemes = TSLexemeUnionOpt(storage->lexemes, lexs, true); + if (oldLexs) + pfree(oldLexs); +} + +/* + * Move all saved lexemes to accepted list + */ +static void +ResultStorageMoveToAccepted(ResultStorage *storage) +{ + if (storage->accepted) + { + TSLexeme *prevAccepted = storage->accepted; + + storage->accepted = TSLexemeUnionOpt(storage->accepted, storage->lexemes, true); + if (prevAccepted) + pfree(prevAccepted); + if (storage->lexemes) + pfree(storage->lexemes); + } + else + { + storage->accepted = storage->lexemes; + } + storage->lexemes = NULL; +} + +/* + * Remove all non-accepted lexemes + */ +static void +ResultStorageClearLexemes(ResultStorage *storage) +{ + if (storage->lexemes) + pfree(storage->lexemes); + storage->lexemes = NULL; +} + +/* + * Remove all accepted lexemes + */ +static void +ResultStorageClearAccepted(ResultStorage *storage) +{ + if (storage->accepted) + pfree(storage->accepted); + storage->accepted = NULL; +} + +/*------------------- + * Condition and command execution + *------------------- + */ + +/* + * Process a token by the dictionary + */ +static TSLexeme * +LexizeExecDictionary(LexizeData *ld, ParsedLex *token, TSMapElement *dictionary) +{ + TSLexeme *res; + TSDictionaryCacheEntry *dict; + DictSubState subState; + Oid dictId = dictionary->value.objectDictionary; + + if (ld->skipDictionary == dictId) + return NULL; + + if (LexemesBufferContains(&ld->buffer, dictionary, token)) + res = LexemesBufferGet(&ld->buffer, dictionary, token); + else + { + char *curValLemm = token->lemm; + int curValLenLemm = token->lenlemm; + DictState *state = DictStateListGet(&ld->dslist, dictId); + + dict = lookup_ts_dictionary_cache(dictId); + + if (state) + { + subState = state->subState; + state->processed = true; + } + else + { + subState.isend = subState.getnext = false; + subState.private_state = NULL; + } + + res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(dict->lexize), + PointerGetDatum(dict->dictData), + PointerGetDatum(curValLemm), + Int32GetDatum(curValLenLemm), + PointerGetDatum(&subState) + )); + + if (subState.getnext) + { + /* + * Dictionary wants next word, so store current context and state + * in the DictStateList + */ + if (state == NULL) + { + state = palloc0(sizeof(DictState)); + state->processed = true; + state->relatedDictionary = dictId; + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + state->acceptedTokens.head = state->acceptedTokens.tail = NULL; + state->tmpResult = NULL; + + /* + * Add state to the list and update pointer in order to work + * with copy from the list + */ + state = DictStateListAdd(&ld->dslist, state); + } + + state->subState = subState; + state->storeToAccepted = res != NULL; + + if (res) + { + if (state->intermediateTokens.head != NULL) + { + ParsedLex *ptr = state->intermediateTokens.head; + + while (ptr) + { + LPLAddTailCopy(&state->acceptedTokens, ptr); + ptr = ptr->next; + } + state->intermediateTokens.head = state->intermediateTokens.tail = NULL; + } + + if (state->tmpResult) + pfree(state->tmpResult); + TSLexemeMarkMulti(res); + state->tmpResult = res; + res = NULL; + } + } + else if (state != NULL) + { + if (res) + { + if (state) + TSLexemeMarkMulti(res); + DictStateListRemove(&ld->dslist, dictId); + } + else + { + /* + * Trigger post-processing in order to check tmpResult and + * restart processing (see LexizeExec function) + */ + state->processed = false; + } + } + LexemesBufferAdd(&ld->buffer, dictionary, token, res); + } + + return res; +} + +/* + * Check is dictionary waits for more tokens or not + */ +static bool +LexizeExecDictionaryWaitNext(LexizeData *ld, Oid dictId) +{ + DictState *state = DictStateListGet(&ld->dslist, dictId); + + if (state) + return state->subState.getnext; + else + return false; +} + +/* + * Check is dictionary result for current token is NULL or not. + * It dictionary waits for more lexemes, the result is interpreted as not null. + */ +static bool +LexizeExecIsNull(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + bool result = false; + + if (config->type == TSMAP_EXPRESSION) + { + TSMapExpression *expression = config->value.objectExpression; + + result = LexizeExecIsNull(ld, token, expression->left) || LexizeExecIsNull(ld, token, expression->right); + } + else if (config->type == TSMAP_DICTIONARY) + { + Oid dictOid = config->value.objectDictionary; + TSLexeme *lexemes = LexizeExecDictionary(ld, token, config); + + if (lexemes) + result = false; + else + result = !LexizeExecDictionaryWaitNext(ld, dictOid); + } + return result; +} + +/* + * Execute a MAP operator + */ +static TSLexeme * +TSLexemeMap(LexizeData *ld, ParsedLex *token, TSMapExpression *expression) +{ + TSLexeme *left_res; + TSLexeme *result = NULL; + int left_size; + int i; + + left_res = LexizeExecTSElement(ld, token, expression->left); + left_size = TSLexemeGetSize(left_res); + + if (left_res == NULL && LexizeExecIsNull(ld, token, expression->left)) + result = LexizeExecTSElement(ld, token, expression->right); + else if (expression->operator == TSMAP_OP_COMMA && + ((left_res != NULL && (left_res->flags & TSL_FILTER) == 0) || left_res == NULL)) + result = left_res; + else + { + TSMapElement *relatedRuleTmp = NULL; + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + relatedRuleTmp->value.objectExpression->left = token->relatedRule; + + for (i = 0; i < left_size; i++) + { + TSLexeme *tmp_res = NULL; + TSLexeme *prev_res; + ParsedLex tmp_token; + + tmp_token.lemm = left_res[i].lexeme; + tmp_token.lenlemm = strlen(left_res[i].lexeme); + tmp_token.type = token->type; + tmp_token.next = NULL; + + tmp_res = LexizeExecTSElement(ld, &tmp_token, expression->right); + relatedRuleTmp->value.objectExpression->right = tmp_token.relatedRule; + prev_res = result; + result = TSLexemeUnion(prev_res, tmp_res); + if (prev_res) + pfree(prev_res); + } + token->relatedRule = relatedRuleTmp; + } + + return result; +} + +/* + * Execute a TSMapElement + * Common point of all possible types of TSMapElement + */ +static TSLexeme * +LexizeExecTSElement(LexizeData *ld, ParsedLex *token, TSMapElement *config) +{ + TSLexeme *result = NULL; + + if (LexemesBufferContains(&ld->buffer, config, token)) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexemesBufferGet(&ld->buffer, config, token); + } + else if (config->type == TSMAP_DICTIONARY) + { + if (ld->debugContext) + token->relatedRule = config; + result = LexizeExecDictionary(ld, token, config); + } + else if (config->type == TSMAP_CASE) + { + TSMapCase *caseObject = config->value.objectCase; + bool conditionIsNull = LexizeExecIsNull(ld, token, caseObject->condition); + + if ((!conditionIsNull && caseObject->match) || (conditionIsNull && !caseObject->match)) + { + if (caseObject->command->type == TSMAP_KEEP) + result = LexizeExecTSElement(ld, token, caseObject->condition); + else + result = LexizeExecTSElement(ld, token, caseObject->command); + } + else if (caseObject->elsebranch) + result = LexizeExecTSElement(ld, token, caseObject->elsebranch); + } + else if (config->type == TSMAP_EXPRESSION) + { + TSLexeme *resLeft = NULL; + TSLexeme *resRight = NULL; + TSMapElement *relatedRuleTmp = NULL; + TSMapExpression *expression = config->value.objectExpression; + + if (expression->operator != TSMAP_OP_MAP && expression->operator != TSMAP_OP_COMMA) + { + if (ld->debugContext) + { + relatedRuleTmp = palloc0(sizeof(TSMapElement)); + relatedRuleTmp->parent = NULL; + relatedRuleTmp->type = TSMAP_EXPRESSION; + relatedRuleTmp->value.objectExpression = palloc0(sizeof(TSMapExpression)); + relatedRuleTmp->value.objectExpression->operator = expression->operator; + } - if (list->head) - list->head = list->head->next; + resLeft = LexizeExecTSElement(ld, token, expression->left); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->left = token->relatedRule; - if (list->head == NULL) - list->tail = NULL; + resRight = LexizeExecTSElement(ld, token, expression->right); + if (ld->debugContext) + relatedRuleTmp->value.objectExpression->right = token->relatedRule; + } - return res; -} + switch (expression->operator) + { + case TSMAP_OP_UNION: + result = TSLexemeUnion(resLeft, resRight); + break; + case TSMAP_OP_EXCEPT: + result = TSLexemeExcept(resLeft, resRight); + break; + case TSMAP_OP_INTERSECT: + result = TSLexemeIntersect(resLeft, resRight); + break; + case TSMAP_OP_MAP: + case TSMAP_OP_COMMA: + result = TSLexemeMap(ld, token, expression); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("text search configuration is invalid"), + errdetail("Text search configuration contains invalid expression operator."))); + break; + } -static void -LexizeAddLemm(LexizeData *ld, int type, char *lemm, int lenlemm) -{ - ParsedLex *newpl = (ParsedLex *) palloc(sizeof(ParsedLex)); + if (ld->debugContext && relatedRuleTmp != NULL) + token->relatedRule = relatedRuleTmp; + } - newpl->type = type; - newpl->lemm = lemm; - newpl->lenlemm = lenlemm; - LPLAddTail(&ld->towork, newpl); - ld->curSub = ld->towork.tail; + if (!LexemesBufferContains(&ld->buffer, config, token)) + LexemesBufferAdd(&ld->buffer, config, token, result); + + return result; } -static void -RemoveHead(LexizeData *ld) +/*------------------- + * LexizeExec and helpers functions + *------------------- + */ + +/* + * Processing of EOF-like token. + * Return all temporary results if any are saved. + */ +static TSLexeme * +LexizeExecFinishProcessing(LexizeData *ld) { - LPLAddTail(&ld->waste, LPLRemoveHead(&ld->towork)); + int i; + TSLexeme *res = NULL; + + for (i = 0; i < ld->dslist.listLength; i++) + { + TSLexeme *last_res = res; - ld->posDict = 0; + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); + } + + return res; } -static void -setCorrLex(LexizeData *ld, ParsedLex **correspondLexem) +/* + * Get last accepted result of the phrase-dictionary + */ +static TSLexeme * +LexizeExecGetPreviousResults(LexizeData *ld) { - if (correspondLexem) - { - *correspondLexem = ld->waste.head; - } - else - { - ParsedLex *tmp, - *ptr = ld->waste.head; + int i; + TSLexeme *res = NULL; - while (ptr) + for (i = 0; i < ld->dslist.listLength; i++) + { + if (!ld->dslist.states[i].processed) { - tmp = ptr->next; - pfree(ptr); - ptr = tmp; + TSLexeme *last_res = res; + + res = TSLexemeUnion(res, ld->dslist.states[i].tmpResult); + if (last_res) + pfree(last_res); } } - ld->waste.head = ld->waste.tail = NULL; + + return res; } +/* + * Remove all dictionary states which wasn't used for current token + */ static void -moveToWaste(LexizeData *ld, ParsedLex *stop) +LexizeExecClearDictStates(LexizeData *ld) { - bool go = true; + int i; - while (ld->towork.head && go) + for (i = 0; i < ld->dslist.listLength; i++) { - if (ld->towork.head == stop) + if (!ld->dslist.states[i].processed) { - ld->curSub = stop->next; - go = false; + DictStateListRemove(&ld->dslist, ld->dslist.states[i].relatedDictionary); + i = 0; } - RemoveHead(ld); } } -static void -setNewTmpRes(LexizeData *ld, ParsedLex *lex, TSLexeme *res) +/* + * Check if there are any dictionaries that didn't processed current token + */ +static bool +LexizeExecNotProcessedDictStates(LexizeData *ld) { - if (ld->tmpRes) - { - TSLexeme *ptr; + int i; - for (ptr = ld->tmpRes; ptr->lexeme; ptr++) - pfree(ptr->lexeme); - pfree(ld->tmpRes); - } - ld->tmpRes = res; - ld->lastRes = lex; + for (i = 0; i < ld->dslist.listLength; i++) + if (!ld->dslist.states[i].processed) + return true; + + return false; } +/* + * Do a lexize processing for a towork queue in LexizeData + */ static TSLexeme * LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) { + ParsedLex *token; + TSMapElement *config; + TSLexeme *res = NULL; + TSLexeme *prevIterationResult = NULL; + bool removeHead = false; + bool resetSkipDictionary = false; + bool accepted = false; int i; - ListDictionary *map; - TSDictionaryCacheEntry *dict; - TSLexeme *res; - if (ld->curDictId == InvalidOid) + for (i = 0; i < ld->dslist.listLength; i++) + ld->dslist.states[i].processed = false; + if (ld->skipDictionary != InvalidOid) + resetSkipDictionary = true; + + token = ld->towork.head; + if (token == NULL) { - /* - * usual mode: dictionary wants only one word, but we should keep in - * mind that we should go through all stack - */ + setCorrLex(ld, correspondLexem); + return NULL; + } - while (ld->towork.head) + if (token->type >= ld->cfg->lenmap) + { + removeHead = true; + } + else + { + config = ld->cfg->map[token->type]; + if (config != NULL) + { + res = LexizeExecTSElement(ld, token, config); + prevIterationResult = LexizeExecGetPreviousResults(ld); + removeHead = prevIterationResult == NULL; + } + else { - ParsedLex *curVal = ld->towork.head; - char *curValLemm = curVal->lemm; - int curValLenLemm = curVal->lenlemm; + removeHead = true; + if (token->type == 0) /* Processing EOF-like token */ + { + res = LexizeExecFinishProcessing(ld); + prevIterationResult = NULL; + } + } - map = ld->cfg->map + curVal->type; + if (LexizeExecNotProcessedDictStates(ld) && (token->type == 0 || config != NULL)) /* Rollback processing */ + { + int i; + ListParsedLex *intermediateTokens = NULL; + ListParsedLex *acceptedTokens = NULL; - if (curVal->type == 0 || curVal->type >= ld->cfg->lenmap || map->len == 0) + for (i = 0; i < ld->dslist.listLength; i++) { - /* skip this type of lexeme */ - RemoveHead(ld); - continue; + if (!ld->dslist.states[i].processed) + { + intermediateTokens = &ld->dslist.states[i].intermediateTokens; + acceptedTokens = &ld->dslist.states[i].acceptedTokens; + if (prevIterationResult == NULL) + ld->skipDictionary = ld->dslist.states[i].relatedDictionary; + } } - for (i = ld->posDict; i < map->len; i++) + if (intermediateTokens && intermediateTokens->head) { - dict = lookup_ts_dictionary_cache(map->dictIds[i]); - - ld->dictState.isend = ld->dictState.getnext = false; - ld->dictState.private_state = NULL; - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curValLemm), - Int32GetDatum(curValLenLemm), - PointerGetDatum(&ld->dictState) - )); - - if (ld->dictState.getnext) + ParsedLex *head = ld->towork.head; + + ld->towork.head = intermediateTokens->head; + intermediateTokens->tail->next = head; + head->next = NULL; + ld->towork.tail = head; + removeHead = false; + LPLClear(&ld->waste); + if (acceptedTokens && acceptedTokens->head) { - /* - * dictionary wants next word, so setup and store current - * position and go to multiword mode - */ - - ld->curDictId = DatumGetObjectId(map->dictIds[i]); - ld->posDict = i + 1; - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - return LexizeExec(ld, correspondLexem); + ld->waste.head = acceptedTokens->head; + ld->waste.tail = acceptedTokens->tail; } + } + ResultStorageClearLexemes(&ld->delayedResults); + if (config != NULL) + res = NULL; + } - if (!res) /* dictionary doesn't know this lexeme */ - continue; + if (config != NULL) + LexizeExecClearDictStates(ld); + else if (token->type == 0) + DictStateListClear(&ld->dslist); + } - if (res->flags & TSL_FILTER) - { - curValLemm = res->lexeme; - curValLenLemm = strlen(res->lexeme); - continue; - } + if (prevIterationResult) + res = prevIterationResult; + else + { + int i; - RemoveHead(ld); - setCorrLex(ld, correspondLexem); - return res; + for (i = 0; i < ld->dslist.listLength; i++) + { + if (ld->dslist.states[i].storeToAccepted) + { + LPLAddTailCopy(&ld->dslist.states[i].acceptedTokens, token); + accepted = true; + ld->dslist.states[i].storeToAccepted = false; + } + else + { + LPLAddTailCopy(&ld->dslist.states[i].intermediateTokens, token); } - - RemoveHead(ld); } } - else - { /* curDictId is valid */ - dict = lookup_ts_dictionary_cache(ld->curDictId); + if (removeHead) + RemoveHead(ld); + + if (ld->dslist.listLength > 0) + { /* - * Dictionary ld->curDictId asks us about following words + * There is at least one thesaurus dictionary in the middle of + * processing. Delay return of the result to avoid wrong lexemes in + * case of thesaurus phrase rejection. */ + ResultStorageAdd(&ld->delayedResults, token, res); + if (accepted) + ResultStorageMoveToAccepted(&ld->delayedResults); - while (ld->curSub) + /* + * Current value of res should not be cleared, because it is stored in + * LexemesBuffer + */ + res = NULL; + } + else + { + if (ld->towork.head == NULL) { - ParsedLex *curVal = ld->curSub; - - map = ld->cfg->map + curVal->type; - - if (curVal->type != 0) - { - bool dictExists = false; - - if (curVal->type >= ld->cfg->lenmap || map->len == 0) - { - /* skip this type of lexeme */ - ld->curSub = curVal->next; - continue; - } + TSLexeme *oldAccepted = ld->delayedResults.accepted; - /* - * We should be sure that current type of lexeme is recognized - * by our dictionary: we just check is it exist in list of - * dictionaries ? - */ - for (i = 0; i < map->len && !dictExists; i++) - if (ld->curDictId == DatumGetObjectId(map->dictIds[i])) - dictExists = true; - - if (!dictExists) - { - /* - * Dictionary can't work with current tpe of lexeme, - * return to basic mode and redo all stored lexemes - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } - } + ld->delayedResults.accepted = TSLexemeUnionOpt(ld->delayedResults.accepted, ld->delayedResults.lexemes, true); + if (oldAccepted) + pfree(oldAccepted); + } - ld->dictState.isend = (curVal->type == 0) ? true : false; - ld->dictState.getnext = false; + /* + * Add accepted delayed results to the output of the parsing. All + * lexemes returned during thesaurus phrase processing should be + * returned simultaneously, since all phrase tokens are processed as + * one. + */ + if (ld->delayedResults.accepted != NULL) + { + /* + * Previous value of res should not be cleared, because it is + * stored in LexemesBuffer + */ + res = TSLexemeUnionOpt(ld->delayedResults.accepted, res, prevIterationResult == NULL); - res = (TSLexeme *) DatumGetPointer(FunctionCall4( - &(dict->lexize), - PointerGetDatum(dict->dictData), - PointerGetDatum(curVal->lemm), - Int32GetDatum(curVal->lenlemm), - PointerGetDatum(&ld->dictState) - )); + ResultStorageClearLexemes(&ld->delayedResults); + ResultStorageClearAccepted(&ld->delayedResults); + } + setCorrLex(ld, correspondLexem); + } - if (ld->dictState.getnext) - { - /* Dictionary wants one more */ - ld->curSub = curVal->next; - if (res) - setNewTmpRes(ld, curVal, res); - continue; - } + if (resetSkipDictionary) + ld->skipDictionary = InvalidOid; - if (res || ld->tmpRes) - { - /* - * Dictionary normalizes lexemes, so we remove from stack all - * used lexemes, return to basic mode and redo end of stack - * (if it exists) - */ - if (res) - { - moveToWaste(ld, ld->curSub); - } - else - { - res = ld->tmpRes; - moveToWaste(ld, ld->lastRes); - } + res = TSLexemeFilterMulti(res); + if (res) + res = TSLexemeRemoveDuplications(res); - /* reset to initial state */ - ld->curDictId = InvalidOid; - ld->posDict = 0; - ld->lastRes = NULL; - ld->tmpRes = NULL; - setCorrLex(ld, correspondLexem); - return res; - } + /* + * Copy result since it may be stored in LexemesBuffere and removed at the + * next step. + */ + if (res) + { + TSLexeme *oldRes = res; + int resSize = TSLexemeGetSize(res); - /* - * Dict don't want next lexem and didn't recognize anything, redo - * from ld->towork.head - */ - ld->curDictId = InvalidOid; - return LexizeExec(ld, correspondLexem); - } + res = palloc0(sizeof(TSLexeme) * (resSize + 1)); + memcpy(res, oldRes, sizeof(TSLexeme) * resSize); } - setCorrLex(ld, correspondLexem); - return NULL; + LexemesBufferClear(&ld->buffer); + return res; } +/*------------------- + * ts_parse API functions + *------------------- + */ + /* * Parse string and lexize words. * @@ -357,7 +1473,7 @@ LexizeExec(LexizeData *ld, ParsedLex **correspondLexem) void parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; @@ -375,36 +1491,42 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } while ((norms = LexizeExec(&ldata, NULL)) != NULL) { - TSLexeme *ptr = norms; + TSLexeme *ptr; + + ptr = norms; prs->pos++; /* set pos */ @@ -429,14 +1551,246 @@ parsetext(Oid cfgId, ParsedText *prs, char *buf, int buflen) } pfree(norms); } - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } +/*------------------- + * ts_debug and helper functions + *------------------- + */ + +/* + * Free memory occupied by temporary TSMapElement + */ + +static void +ts_debug_free_rule(TSMapElement *element) +{ + if (element != NULL && element->type == TSMAP_EXPRESSION) + { + ts_debug_free_rule(element->value.objectExpression->left); + ts_debug_free_rule(element->value.objectExpression->right); + pfree(element->value.objectExpression); + pfree(element); + } +} + +/* + * Initialize SRF context and text parser for ts_debug execution. + */ +static void +ts_debug_init(Oid cfgId, text *inputText, FunctionCallInfo fcinfo) +{ + TupleDesc tupdesc; + char *buf; + int buflen; + FuncCallContext *funcctx; + MemoryContext oldcontext; + TSDebugContext *context; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + buf = text_to_cstring(inputText); + buflen = strlen(buf); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + funcctx->user_fctx = palloc0(sizeof(TSDebugContext)); + funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + context = funcctx->user_fctx; + context->cfg = lookup_ts_config_cache(cfgId); + context->prsobj = lookup_ts_parser_cache(context->cfg->prsId); + + context->tokenTypes = (LexDescr *) DatumGetPointer(OidFunctionCall1(context->prsobj->lextypeOid, + (Datum) 0)); + + context->prsdata = (void *) DatumGetPointer(FunctionCall2(&context->prsobj->prsstart, + PointerGetDatum(buf), + Int32GetDatum(buflen))); + LexizeInit(&context->ldata, context->cfg); + context->ldata.debugContext = true; + context->tokentype = 1; + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Get one token from input text and add it to processing queue. + */ +static void +ts_debug_get_token(FuncCallContext *funcctx) +{ + TSDebugContext *context; + MemoryContext oldcontext; + int lenlemm; + char *lemm = NULL; + + context = funcctx->user_fctx; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + context->tokentype = DatumGetInt32(FunctionCall3(&(context->prsobj->prstoken), + PointerGetDatum(context->prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (context->tokentype > 0 && lenlemm >= MAXSTRLEN) + { +#ifdef IGNORE_LONGLEXEME + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#else + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); +#endif + } + + LexizeAddLemm(&context->ldata, context->tokentype, lemm, lenlemm); + MemoryContextSwitchTo(oldcontext); +} + /* + * Parse text and print debug information, such as token type, dictionary map + * configuration, selected command and lexemes for each token. + * Arguments: regconfiguration(Oid) cfgId, text *inputText + */ +Datum +ts_debug(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TSDebugContext *context; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) + { + Oid cfgId = PG_GETARG_OID(0); + text *inputText = PG_GETARG_TEXT_P(1); + + ts_debug_init(cfgId, inputText, fcinfo); + } + + funcctx = SRF_PERCALL_SETUP(); + context = funcctx->user_fctx; + + while (context->tokentype > 0 && context->leftTokens == NULL) + { + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + ts_debug_get_token(funcctx); + + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + MemoryContextSwitchTo(oldcontext); + } + + while (context->leftTokens == NULL && context->ldata.towork.head != NULL) + context->savedLexemes = LexizeExec(&context->ldata, &(context->leftTokens)); + + if (context->leftTokens && context->leftTokens && context->leftTokens->type > 0) + { + HeapTuple tuple; + Datum result; + char **values; + ParsedLex *lex = context->leftTokens; + StringInfo str = NULL; + TSLexeme *ptr; + + values = palloc0(sizeof(char *) * 7); + str = makeStringInfo(); + initStringInfo(str); + + values[0] = context->tokenTypes[lex->type - 1].alias; + values[1] = context->tokenTypes[lex->type - 1].descr; + + values[2] = palloc0(sizeof(char) * (lex->lenlemm + 1)); + memcpy(values[2], lex->lemm, sizeof(char) * lex->lenlemm); + + initStringInfo(str); + appendStringInfoChar(str, '{'); + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + Oid *dictionaries = TSMapGetDictionaries(context->ldata.cfg->map[lex->type]); + Oid *currentDictionary = NULL; + for (currentDictionary = dictionaries; *currentDictionary != InvalidOid; currentDictionary++) + { + if (currentDictionary != dictionaries) + appendStringInfoChar(str, ','); + + TSMapPrintDictName(*currentDictionary, str); + } + } + appendStringInfoChar(str, '}'); + values[3] = str->data; + + if (lex->type < context->ldata.cfg->lenmap && context->ldata.cfg->map[lex->type]) + { + initStringInfo(str); + TSMapPrintElement(context->ldata.cfg->map[lex->type], str); + values[4] = str->data; + + initStringInfo(str); + if (lex->relatedRule) + { + TSMapPrintElement(lex->relatedRule, str); + values[5] = str->data; + str = makeStringInfo(); + initStringInfo(str); + ts_debug_free_rule(lex->relatedRule); + lex->relatedRule = NULL; + } + } + + initStringInfo(str); + ptr = context->savedLexemes; + if (context->savedLexemes) + appendStringInfoChar(str, '{'); + + while (ptr && ptr->lexeme) + { + if (ptr != context->savedLexemes) + appendStringInfoString(str, ", "); + appendStringInfoString(str, ptr->lexeme); + ptr++; + } + if (context->savedLexemes) + appendStringInfoChar(str, '}'); + if (context->savedLexemes) + values[6] = str->data; + else + values[6] = NULL; + + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + result = HeapTupleGetDatum(tuple); + + context->leftTokens = lex->next; + pfree(lex); + if (context->leftTokens == NULL && context->savedLexemes) + pfree(context->savedLexemes); + + SRF_RETURN_NEXT(funcctx, result); + } + + FunctionCall1(&(context->prsobj->prsend), PointerGetDatum(context->prsdata)); + SRF_RETURN_DONE(funcctx); +} + +/*------------------- * Headline framework + *------------------- */ + static void hladdword(HeadlineParsedText *prs, char *buf, int buflen, int type) { @@ -532,12 +1886,12 @@ addHLParsedLex(HeadlineParsedText *prs, TSQuery query, ParsedLex *lexs, TSLexeme void hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int buflen) { - int type, + int type = -1, lenlemm; char *lemm = NULL; LexizeData ldata; TSLexeme *norms; - ParsedLex *lexs; + ParsedLex *lexs = NULL; TSConfigCacheEntry *cfg; TSParserCacheEntry *prsobj; void *prsdata; @@ -551,32 +1905,36 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu LexizeInit(&ldata, cfg); + type = 1; do { - type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), - PointerGetDatum(prsdata), - PointerGetDatum(&lemm), - PointerGetDatum(&lenlemm))); - - if (type > 0 && lenlemm >= MAXSTRLEN) + if (type > 0) { + type = DatumGetInt32(FunctionCall3(&(prsobj->prstoken), + PointerGetDatum(prsdata), + PointerGetDatum(&lemm), + PointerGetDatum(&lenlemm))); + + if (type > 0 && lenlemm >= MAXSTRLEN) + { #ifdef IGNORE_LONGLEXEME - ereport(NOTICE, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); - continue; + ereport(NOTICE, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); + continue; #else - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("word is too long to be indexed"), - errdetail("Words longer than %d characters are ignored.", - MAXSTRLEN))); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long to be indexed"), + errdetail("Words longer than %d characters are ignored.", + MAXSTRLEN))); #endif - } + } - LexizeAddLemm(&ldata, type, lemm, lenlemm); + LexizeAddLemm(&ldata, type, lemm, lenlemm); + } do { @@ -587,9 +1945,10 @@ hlparsetext(Oid cfgId, HeadlineParsedText *prs, TSQuery query, char *buf, int bu } else addHLParsedLex(prs, query, lexs, NULL); + lexs = NULL; } while (norms); - } while (type > 0); + } while (type > 0 || ldata.towork.head); FunctionCall1(&(prsobj->prsend), PointerGetDatum(prsdata)); } @@ -642,14 +2001,14 @@ generateHeadline(HeadlineParsedText *prs) } else if (!wrd->skip) { - if (wrd->selected) + if (wrd->selected && (wrd == prs->words || !(wrd - 1)->selected)) { memcpy(ptr, prs->startsel, prs->startsellen); ptr += prs->startsellen; } memcpy(ptr, wrd->word, wrd->len); ptr += wrd->len; - if (wrd->selected) + if (wrd->selected && ((wrd + 1 - prs->words) == prs->curwords || !(wrd + 1)->selected)) { memcpy(ptr, prs->stopsel, prs->stopsellen); ptr += prs->stopsellen; diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c index f6e03ae..0dd846b 100644 --- a/src/backend/tsearch/ts_utils.c +++ b/src/backend/tsearch/ts_utils.c @@ -20,7 +20,6 @@ #include "tsearch/ts_locale.h" #include "tsearch/ts_utils.h" - /* * Given the base name and extension of a tsearch config file, return * its full path name. The base name is assumed to be user-supplied, diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 2b38178..f251e83 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -828,11 +828,10 @@ static const struct cachedesc cacheinfo[] = { }, {TSConfigMapRelationId, /* TSCONFIGMAP */ TSConfigMapIndexId, - 3, + 2, { Anum_pg_ts_config_map_mapcfg, Anum_pg_ts_config_map_maptokentype, - Anum_pg_ts_config_map_mapseqno, 0 }, 2 diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index f11cba4..c0f98ba 100644 --- a/src/backend/utils/cache/ts_cache.c +++ b/src/backend/utils/cache/ts_cache.c @@ -39,6 +39,7 @@ #include "catalog/pg_ts_template.h" #include "commands/defrem.h" #include "tsearch/ts_cache.h" +#include "tsearch/ts_configmap.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" @@ -51,13 +52,12 @@ /* - * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size + * MAXTOKENTYPE is arbitrary limits on the workspace size * used in lookup_ts_config_cache(). We could avoid hardwiring a limit * by making the workspace dynamically enlargeable, but it seems unlikely * to be worth the trouble. */ -#define MAXTOKENTYPE 256 -#define MAXDICTSPERTT 100 +#define MAXTOKENTYPE 256 static HTAB *TSParserCacheHash = NULL; @@ -418,11 +418,10 @@ lookup_ts_config_cache(Oid cfgId) ScanKeyData mapskey; SysScanDesc mapscan; HeapTuple maptup; - ListDictionary maplists[MAXTOKENTYPE + 1]; - Oid mapdicts[MAXDICTSPERTT]; + TSMapElement *mapconfigs[MAXTOKENTYPE + 1]; int maxtokentype; - int ndicts; int i; + TSMapElement *tmpConfig; tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); if (!HeapTupleIsValid(tp)) @@ -453,8 +452,8 @@ lookup_ts_config_cache(Oid cfgId) if (entry->map) { for (i = 0; i < entry->lenmap; i++) - if (entry->map[i].dictIds) - pfree(entry->map[i].dictIds); + if (entry->map[i]) + TSMapElementFree(entry->map[i]); pfree(entry->map); } } @@ -468,13 +467,11 @@ lookup_ts_config_cache(Oid cfgId) /* * Scan pg_ts_config_map to gather dictionary list for each token type * - * Because the index is on (mapcfg, maptokentype, mapseqno), we will - * see the entries in maptokentype order, and in mapseqno order for - * each token type, even though we didn't explicitly ask for that. + * Because the index is on (mapcfg, maptokentype), we will see the + * entries in maptokentype order even though we didn't explicitly ask + * for that. */ - MemSet(maplists, 0, sizeof(maplists)); maxtokentype = 0; - ndicts = 0; ScanKeyInit(&mapskey, Anum_pg_ts_config_map_mapcfg, @@ -486,6 +483,7 @@ lookup_ts_config_cache(Oid cfgId) mapscan = systable_beginscan_ordered(maprel, mapidx, NULL, 1, &mapskey); + memset(mapconfigs, 0, sizeof(mapconfigs)); while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) { Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); @@ -495,51 +493,27 @@ lookup_ts_config_cache(Oid cfgId) elog(ERROR, "maptokentype value %d is out of range", toktype); if (toktype < maxtokentype) elog(ERROR, "maptokentype entries are out of order"); - if (toktype > maxtokentype) - { - /* starting a new token type, but first save the prior data */ - if (ndicts > 0) - { - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - } - maxtokentype = toktype; - mapdicts[0] = cfgmap->mapdict; - ndicts = 1; - } - else - { - /* continuing data for current token type */ - if (ndicts >= MAXDICTSPERTT) - elog(ERROR, "too many pg_ts_config_map entries for one token type"); - mapdicts[ndicts++] = cfgmap->mapdict; - } + + maxtokentype = toktype; + tmpConfig = JsonbToTSMap(DatumGetJsonbP(&cfgmap->mapdicts)); + mapconfigs[maxtokentype] = TSMapMoveToMemoryContext(tmpConfig, CacheMemoryContext); + TSMapElementFree(tmpConfig); + tmpConfig = NULL; } systable_endscan_ordered(mapscan); index_close(mapidx, AccessShareLock); heap_close(maprel, AccessShareLock); - if (ndicts > 0) + if (maxtokentype > 0) { - /* save the last token type's dictionaries */ - maplists[maxtokentype].len = ndicts; - maplists[maxtokentype].dictIds = (Oid *) - MemoryContextAlloc(CacheMemoryContext, - sizeof(Oid) * ndicts); - memcpy(maplists[maxtokentype].dictIds, mapdicts, - sizeof(Oid) * ndicts); - /* and save the overall map */ + /* save the overall map */ entry->lenmap = maxtokentype + 1; - entry->map = (ListDictionary *) + entry->map = (TSMapElement * *) MemoryContextAlloc(CacheMemoryContext, - sizeof(ListDictionary) * entry->lenmap); - memcpy(entry->map, maplists, - sizeof(ListDictionary) * entry->lenmap); + sizeof(TSMapElement *) * entry->lenmap); + memcpy(entry->map, mapconfigs, + sizeof(TSMapElement *) * entry->lenmap); } entry->isvalid = true; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 93c869f..ff936c3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -14255,15 +14255,29 @@ dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo) PQclear(res); resetPQExpBuffer(query); - appendPQExpBuffer(query, - "SELECT\n" - " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" - " m.mapdict::pg_catalog.regdictionary AS dictname\n" - "FROM pg_catalog.pg_ts_config_map AS m\n" - "WHERE m.mapcfg = '%u'\n" - "ORDER BY m.mapcfg, m.maptokentype, m.mapseqno", - cfginfo->cfgparser, cfginfo->dobj.catId.oid); + + if (fout->remoteVersion >= 110000) + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); + else + appendPQExpBuffer(query, + "SELECT\n" + " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS tokenname,\n" + " m.mapdict::pg_catalog.regdictionary AS dictname\n" + "FROM pg_catalog.pg_ts_config_map AS m\n" + "WHERE m.mapcfg = '%u'\n" + "GROUP BY m.mapcfg, m.maptokentype, m.mapseqno\n" + "ORDER BY m.mapcfg, m.maptokentype", + cfginfo->cfgparser, cfginfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 410131e..95e3a89 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -4646,25 +4646,41 @@ describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname, initPQExpBuffer(&buf); - printfPQExpBuffer(&buf, - "SELECT\n" - " ( SELECT t.alias FROM\n" - " pg_catalog.ts_token_type(c.cfgparser) AS t\n" - " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" - " pg_catalog.btrim(\n" - " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" - " FROM pg_catalog.pg_ts_config_map AS mm\n" - " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" - " ORDER BY mapcfg, maptokentype, mapseqno\n" - " ) :: pg_catalog.text,\n" - " '{}') AS \"%s\"\n" - "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" - "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" - "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" - "ORDER BY 1;", - gettext_noop("Token"), - gettext_noop("Dictionaries"), - oid); + if (pset.sversion >= 110000) + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " dictionary_mapping_to_text(m.mapcfg, m.maptokentype) AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + else + printfPQExpBuffer(&buf, + "SELECT\n" + " ( SELECT t.alias FROM\n" + " pg_catalog.ts_token_type(c.cfgparser) AS t\n" + " WHERE t.tokid = m.maptokentype ) AS \"%s\",\n" + " pg_catalog.btrim(\n" + " ARRAY( SELECT mm.mapdict::pg_catalog.regdictionary\n" + " FROM pg_catalog.pg_ts_config_map AS mm\n" + " WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype\n" + " ORDER BY mapcfg, maptokentype, mapseqno\n" + " ) :: pg_catalog.text,\n" + " '{}') AS \"%s\"\n" + "FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m\n" + "WHERE c.oid = '%s' AND m.mapcfg = c.oid\n" + "GROUP BY m.mapcfg, m.maptokentype, c.cfgparser\n" + "ORDER BY 1;", + gettext_noop("Token"), + gettext_noop("Dictionaries"), + oid); + res = PSQLexec(buf.data); termPQExpBuffer(&buf); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 42499e2..79fb3f0 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -262,7 +262,7 @@ DECLARE_UNIQUE_INDEX(pg_ts_config_cfgname_index, 3608, on pg_ts_config using btr DECLARE_UNIQUE_INDEX(pg_ts_config_oid_index, 3712, on pg_ts_config using btree(oid oid_ops)); #define TSConfigOidIndexId 3712 -DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops, mapseqno int4_ops)); +DECLARE_UNIQUE_INDEX(pg_ts_config_map_index, 3609, on pg_ts_config_map using btree(mapcfg oid_ops, maptokentype int4_ops)); #define TSConfigMapIndexId 3609 DECLARE_UNIQUE_INDEX(pg_ts_dict_dictname_index, 3604, on pg_ts_dict using btree(dictname name_ops, dictnamespace oid_ops)); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index f3b9c33..775f92e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9020,6 +9020,19 @@ prorettype => 'regconfig', proargtypes => '', prosrc => 'get_current_ts_config' }, +{ oid => '8891', descr => 'returns text representation of dictionary configuration map', + proname => 'dictionary_mapping_to_text', provolatile => 's', + prorettype => 'text', proargtypes => 'regconfig int4', + prosrc => 'dictionary_mapping_to_text' }, + +{ oid => '8892', descr => 'debug function for a text search configuration', + proname => 'ts_debug', provolatile => 's', + prorettype => 'record', proargtypes => 'regconfig text', + proallargtypes => '{regconfig,text,text,text,text,_regdictionary,text,text,_text}', + proargmodes => '{i,i,o,o,o,o,o,o,o}', + proargnames => '{ftsconfig,inputext,alias,description,token,dictionaries,configuration,command,lexemes}', + prosrc => 'ts_debug' }, + { oid => '3736', descr => 'I/O', proname => 'regconfigin', provolatile => 's', prorettype => 'regconfig', proargtypes => 'cstring', prosrc => 'regconfigin' }, diff --git a/src/include/catalog/pg_ts_config_map.dat b/src/include/catalog/pg_ts_config_map.dat index 090a1ca..4aa3612 100644 --- a/src/include/catalog/pg_ts_config_map.dat +++ b/src/include/catalog/pg_ts_config_map.dat @@ -12,24 +12,24 @@ [ -{ mapcfg => '3748', maptokentype => '1', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '2', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '3', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '4', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '5', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '6', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '7', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '8', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '9', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '10', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '11', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '15', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '16', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '17', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '18', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '19', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '20', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '21', mapseqno => '1', mapdict => '3765' }, -{ mapcfg => '3748', maptokentype => '22', mapseqno => '1', mapdict => '3765' }, +{ mapcfg => '3748', maptokentype => '1', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '2', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '3', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '4', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '5', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '6', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '7', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '8', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '9', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '10', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '11', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '15', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '16', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '17', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '18', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '19', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '20', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '21', mapdicts => '[3765]' }, +{ mapcfg => '3748', maptokentype => '22', mapdicts => '[3765]' }, ] diff --git a/src/include/catalog/pg_ts_config_map.h b/src/include/catalog/pg_ts_config_map.h index 2120021..aed1b20 100644 --- a/src/include/catalog/pg_ts_config_map.h +++ b/src/include/catalog/pg_ts_config_map.h @@ -19,6 +19,7 @@ #define PG_TS_CONFIG_MAP_H #include "catalog/genbki.h" +#include "utils/jsonb.h" #include "catalog/pg_ts_config_map_d.h" /* ---------------- @@ -26,14 +27,91 @@ * typedef struct FormData_pg_ts_config_map * ---------------- */ +#define TSConfigMapRelationId 3603 + +/* + * Create a typedef in order to use same type name in + * generated DB initialization script and C source code + */ +typedef Jsonb jsonb; + CATALOG(pg_ts_config_map,3603,TSConfigMapRelationId) BKI_WITHOUT_OIDS { Oid mapcfg; /* OID of configuration owning this entry */ int32 maptokentype; /* token type from parser */ - int32 mapseqno; /* order in which to consult dictionaries */ - Oid mapdict; /* dictionary to consult */ + + /* + * mapdicts is the only one variable-length field so it is safe to use + * it directly, without hiding from C interface. + */ + jsonb mapdicts; /* dictionary map Jsonb representation */ } FormData_pg_ts_config_map; typedef FormData_pg_ts_config_map *Form_pg_ts_config_map; +/* + * Element of the mapping expression tree + */ +typedef struct TSMapElement +{ + int type; /* Type of the element */ + union + { + struct TSMapExpression *objectExpression; + struct TSMapCase *objectCase; + Oid objectDictionary; + void *object; + } value; + struct TSMapElement *parent; /* Parent in the expression tree */ +} TSMapElement; + +/* + * Representation of expression with operator and two operands + */ +typedef struct TSMapExpression +{ + int operator; + TSMapElement *left; + TSMapElement *right; +} TSMapExpression; + +/* + * Representation of CASE structure inside database + */ +typedef struct TSMapCase +{ + TSMapElement *condition; + TSMapElement *command; + TSMapElement *elsebranch; + bool match; /* If false, NO MATCH is used */ +} TSMapCase; + +/* ---------------- + * Compiler constants for pg_ts_config_map + * ---------------- + */ +#define Natts_pg_ts_config_map 3 +#define Anum_pg_ts_config_map_mapcfg 1 +#define Anum_pg_ts_config_map_maptokentype 2 +#define Anum_pg_ts_config_map_mapdicts 3 + +/* ---------------- + * Dictionary map operators + * ---------------- + */ +#define TSMAP_OP_MAP 1 +#define TSMAP_OP_UNION 2 +#define TSMAP_OP_EXCEPT 3 +#define TSMAP_OP_INTERSECT 4 +#define TSMAP_OP_COMMA 5 + +/* ---------------- + * TSMapElement object types + * ---------------- + */ +#define TSMAP_EXPRESSION 1 +#define TSMAP_CASE 2 +#define TSMAP_DICTIONARY 3 +#define TSMAP_KEEP 4 + #endif /* PG_TS_CONFIG_MAP_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index defdbae..0460cc5 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -388,6 +388,9 @@ typedef enum NodeTag T_CreateEnumStmt, T_CreateRangeStmt, T_AlterEnumStmt, + T_DictMapExprElem, + T_DictMapElem, + T_DictMapCase, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, T_CreateFdwStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index c840538..c88f658 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3454,6 +3454,50 @@ typedef enum AlterTSConfigType ALTER_TSCONFIG_DROP_MAPPING } AlterTSConfigType; +/* + * TS Configuration expression tree element's types + */ +typedef enum DictMapElemType +{ + DICT_MAP_CASE, + DICT_MAP_EXPRESSION, + DICT_MAP_KEEP, + DICT_MAP_DICTIONARY +} DictMapElemType; + +/* + * TS Configuration expression tree abstract element + */ +typedef struct DictMapElem +{ + NodeTag type; + int8 kind; /* See DictMapElemType */ + void *data; /* Type should be detected by kind value */ +} DictMapElem; + +/* + * TS Configuration expression tree element with operator and operands + */ +typedef struct DictMapExprElem +{ + NodeTag type; + DictMapElem *left; + DictMapElem *right; + int8 oper; +} DictMapExprElem; + +/* + * TS Configuration expression tree CASE element + */ +typedef struct DictMapCase +{ + NodeTag type; + struct DictMapElem *condition; + struct DictMapElem *command; + struct DictMapElem *elsebranch; + bool match; +} DictMapCase; + typedef struct AlterTSConfigurationStmt { NodeTag type; @@ -3466,6 +3510,7 @@ typedef struct AlterTSConfigurationStmt */ List *tokentype; /* list of Value strings */ List *dicts; /* list of list of Value strings */ + DictMapElem *dict_map; /* tree of the mapping expression */ bool override; /* if true - remove old variant */ bool replace; /* if true - replace dictionary by another */ bool missing_ok; /* for DROP - skip error if missing? */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 81f758a..e0b790f 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -221,6 +221,7 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("keep", KEEP, RESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) @@ -243,6 +244,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD) +PG_KEYWORD("map", MAP, UNRESERVED_KEYWORD) PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD) diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h index 410f1d5..4633dd7 100644 --- a/src/include/tsearch/ts_cache.h +++ b/src/include/tsearch/ts_cache.h @@ -14,6 +14,7 @@ #define TS_CACHE_H #include "utils/guc.h" +#include "catalog/pg_ts_config_map.h" /* @@ -66,6 +67,7 @@ typedef struct { int len; Oid *dictIds; + int32 *dictOptions; } ListDictionary; typedef struct @@ -77,7 +79,7 @@ typedef struct Oid prsId; int lenmap; - ListDictionary *map; + TSMapElement **map; } TSConfigCacheEntry; diff --git a/src/include/tsearch/ts_configmap.h b/src/include/tsearch/ts_configmap.h new file mode 100644 index 0000000..79e6180 --- /dev/null +++ b/src/include/tsearch/ts_configmap.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * ts_configmap.h + * internal representation of text search configuration and utilities for it + * + * Copyright (c) 1998-2018, PostgreSQL Global Development Group + * + * src/include/tsearch/ts_utils.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PG_TS_CONFIGMAP_H_ +#define _PG_TS_CONFIGMAP_H_ + +#include "utils/jsonb.h" +#include "catalog/pg_ts_config_map.h" + +/* + * Configuration storage functions + * Provide interface to convert ts_configuration into JSONB and vice versa + */ + +/* Convert TSMapElement structure into JSONB */ +extern Jsonb *TSMapToJsonb(TSMapElement *config); + +/* Extract TSMapElement from JSONB formated data */ +extern TSMapElement *JsonbToTSMap(Jsonb *json); +/* Replace all occurances of oldDict by newDict */ +extern void TSMapReplaceDictionary(TSMapElement *config, Oid oldDict, Oid newDict); + +/* Move rule list into specified memory context */ +extern TSMapElement *TSMapMoveToMemoryContext(TSMapElement *config, MemoryContext context); +/* Free all nodes of the rule list */ +extern void TSMapElementFree(TSMapElement *element); + +/* Print map in human-readable format */ +extern void TSMapPrintElement(TSMapElement *config, StringInfo result); + +/* Print dictionary name for a given Oid */ +extern void TSMapPrintDictName(Oid dictId, StringInfo result); + +/* Return all dictionaries used in config */ +extern Oid *TSMapGetDictionaries(TSMapElement *config); + +/* Do a deep comparison of two TSMapElements. Doesn't check parents of elements */ +extern bool TSMapElementEquals(TSMapElement *a, TSMapElement *b); + +#endif /* _PG_TS_CONFIGMAP_H_ */ diff --git a/src/include/tsearch/ts_public.h b/src/include/tsearch/ts_public.h index 0b7a5aa..d970eec 100644 --- a/src/include/tsearch/ts_public.h +++ b/src/include/tsearch/ts_public.h @@ -115,6 +115,7 @@ typedef struct #define TSL_ADDPOS 0x01 #define TSL_PREFIX 0x02 #define TSL_FILTER 0x04 +#define TSL_MULTI 0x08 /* * Struct for supporting complex dictionaries like thesaurus. diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index d56c70c..08c2674 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -1089,14 +1089,6 @@ WHERE mapcfg != 0 AND ------+-------- (0 rows) -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); - ctid | mapdict -------+--------- -(0 rows) - SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/expected/tsdicts.out b/src/test/regress/expected/tsdicts.out index 0c1d7c7..512af59 100644 --- a/src/test/regress/expected/tsdicts.out +++ b/src/test/regress/expected/tsdicts.out @@ -420,6 +420,105 @@ SELECT ts_lexize('thesaurus', 'one'); {1} (1 row) +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; +SELECT to_tsvector('english_union', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_union', 'books'); + to_tsvector +-------------------- + 'book':1 'books':1 +(1 row) + +SELECT to_tsvector('english_union', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; +SELECT to_tsvector('english_intersect', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_intersect', 'books'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_intersect', 'booking'); + to_tsvector +------------- + +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; +SELECT to_tsvector('english_except', 'book'); + to_tsvector +------------- + +(1 row) + +SELECT to_tsvector('english_except', 'books'); + to_tsvector +------------- + 'books':1 +(1 row) + +SELECT to_tsvector('english_except', 'booking'); + to_tsvector +------------- + 'booking':1 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; +SELECT to_tsvector('english_branches', 'book'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'books'); + to_tsvector +------------- + 'book':1 +(1 row) + +SELECT to_tsvector('english_branches', 'booking'); + to_tsvector +---------------------- + 'book':1 'booking':1 +(1 row) + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -580,6 +679,163 @@ SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a 'card':3,10 'invit':2,9 'like':6 'look':5 'order':1,8 (1 row) +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +--------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +----------------------------------------------------- + '1987a':6 'mysteri':2 'ring':3 'sn':5 'supernova':5 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +------------------------------------------------------------------------ + '1987a':6 'mysterious':2 'of':4 'rings':3 'sn':5 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------------------- + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN MATCH THEN simple UNION thesaurus+ + | ELSE simple + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------------------ + '12':1 'one':1 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +----------------------------------- + '123':1 'one':1 'three':3 'two':2 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two four'); + to_tsvector +--------------------------------- + '12':1 'four':3 'one':1 'two':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst + Text search configuration "public.thesaurus_tst" +Parser: "pg_catalog.default" + Token | Dictionaries +-----------------+------------------------------------------ + asciihword | synonym, thesaurus, english_stem + asciiword | CASE thesaurus WHEN NO MATCH THEN simple+ + | ELSE thesaurus + + | END + email | simple + file | simple + float | simple + host | simple + hword | english_stem + hword_asciipart | synonym, thesaurus, english_stem + hword_numpart | simple + hword_part | english_stem + int | simple + numhword | simple + numword | simple + sfloat | simple + uint | simple + url | simple + url_path | simple + version | simple + word | english_stem + +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +------------------ + '12':1 'books':2 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); + to_tsvector +------------- + '12':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two three'); + to_tsvector +------------- + '123':1 +(1 row) + +SELECT to_tsvector('thesaurus_tst', 'one two books'); + to_tsvector +----------------- + '12':1 'book':2 +(1 row) + +CREATE TEXT SEARCH CONFIGURATION operators_tst ( + COPY=thesaurus_tst +); +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A'); + to_tsvector +-------------------------------------------------------------------------------------- + '1987a':6 'mysteri':2 'mysterious':2 'of':4 'ring':3 'rings':3 'supernova':5 'the':1 +(1 row) + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION (synonym, simple); +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A Postgres'); + to_tsvector +----------------------------------------------------------------------------------------------------------- + '1987a':6 'mysteri':2 'mysterious':2 'of':4 'pgsql':7 'postgr':7 'ring':3 'rings':3 'supernova':5 'the':1 +(1 row) + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index b088ff0..9ebf5b9 100644 --- a/src/test/regress/expected/tsearch.out +++ b/src/test/regress/expected/tsearch.out @@ -36,11 +36,11 @@ WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -----+--------- (0 rows) -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; - mapcfg | maptokentype | mapseqno ---------+--------------+---------- +WHERE mapcfg = 0; + mapcfg | maptokentype +--------+-------------- (0 rows) -- Look for pg_ts_config_map entries that aren't one of parser's token types @@ -51,8 +51,8 @@ RIGHT JOIN pg_ts_config_map AS m ON (tt.cfgid=m.mapcfg AND tt.tokid=m.maptokentype) WHERE tt.cfgid IS NULL OR tt.tokid IS NULL; - cfgid | tokid | mapcfg | maptokentype | mapseqno | mapdict --------+-------+--------+--------------+----------+--------- + cfgid | tokid | mapcfg | maptokentype | mapdicts +-------+-------+--------+--------------+---------- (0 rows) -- test basic text search behavior without indexes, then with @@ -567,55 +567,55 @@ SELECT length(to_tsvector('english', '345 [email protected] '' http://www.com/ http://ae -- ts_debug SELECT * from ts_debug('english', '<myns:foo-bar_baz.blurfl>abc&nm1;def©ghiõjkl</myns:foo-bar_baz.blurfl>'); - alias | description | token | dictionaries | dictionary | lexemes ------------+-----------------+----------------------------+----------------+--------------+--------- - tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | - asciiword | Word, all ASCII | abc | {english_stem} | english_stem | {abc} - entity | XML entity | &nm1; | {} | | - asciiword | Word, all ASCII | def | {english_stem} | english_stem | {def} - entity | XML entity | © | {} | | - asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | {ghi} - entity | XML entity | õ | {} | | - asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | {jkl} - tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +-----------+-----------------+----------------------------+----------------+---------------+--------------+--------- + tag | XML tag | <myns:foo-bar_baz.blurfl> | {} | | | + asciiword | Word, all ASCII | abc | {english_stem} | english_stem | english_stem | {abc} + entity | XML entity | &nm1; | {} | | | + asciiword | Word, all ASCII | def | {english_stem} | english_stem | english_stem | {def} + entity | XML entity | © | {} | | | + asciiword | Word, all ASCII | ghi | {english_stem} | english_stem | english_stem | {ghi} + entity | XML entity | õ | {} | | | + asciiword | Word, all ASCII | jkl | {english_stem} | english_stem | english_stem | {jkl} + tag | XML tag | </myns:foo-bar_baz.blurfl> | {} | | | (9 rows) -- check parsing of URLs SELECT * from ts_debug('english', 'http://www.harewoodsolutions.co.uk/press.aspx</span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------------------+--------------+------------+------------------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | {www.harewoodsolutions.co.uk/press.aspx} - host | Host | www.harewoodsolutions.co.uk | {simple} | simple | {www.harewoodsolutions.co.uk} - url_path | URL path | /press.aspx | {simple} | simple | {/press.aspx} - tag | XML tag | </span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------------------+--------------+---------------+---------+------------------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | www.harewoodsolutions.co.uk/press.aspx | {simple} | simple | simple | {www.harewoodsolutions.co.uk/press.aspx} + host | Host | www.harewoodsolutions.co.uk | {simple} | simple | simple | {www.harewoodsolutions.co.uk} + url_path | URL path | /press.aspx | {simple} | simple | simple | {/press.aspx} + tag | XML tag | </span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://aew.wer0c.ewr/id?ad=qwe&dw<span>'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------------+--------------+------------+------------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | {aew.wer0c.ewr/id?ad=qwe&dw} - host | Host | aew.wer0c.ewr | {simple} | simple | {aew.wer0c.ewr} - url_path | URL path | /id?ad=qwe&dw | {simple} | simple | {/id?ad=qwe&dw} - tag | XML tag | <span> | {} | | + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------------+--------------+---------------+---------+------------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | aew.wer0c.ewr/id?ad=qwe&dw | {simple} | simple | simple | {aew.wer0c.ewr/id?ad=qwe&dw} + host | Host | aew.wer0c.ewr | {simple} | simple | simple | {aew.wer0c.ewr} + url_path | URL path | /id?ad=qwe&dw | {simple} | simple | simple | {/id?ad=qwe&dw} + tag | XML tag | <span> | {} | | | (5 rows) SELECT * from ts_debug('english', 'http://5aew.werc.ewr:8100/?'); - alias | description | token | dictionaries | dictionary | lexemes -----------+---------------+----------------------+--------------+------------+------------------------ - protocol | Protocol head | http:// | {} | | - url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | {5aew.werc.ewr:8100/?} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /? | {simple} | simple | {/?} + alias | description | token | dictionaries | configuration | command | lexemes +----------+---------------+----------------------+--------------+---------------+---------+------------------------ + protocol | Protocol head | http:// | {} | | | + url | URL | 5aew.werc.ewr:8100/? | {simple} | simple | simple | {5aew.werc.ewr:8100/?} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /? | {simple} | simple | simple | {/?} (4 rows) SELECT * from ts_debug('english', '5aew.werc.ewr:8100/?xx'); - alias | description | token | dictionaries | dictionary | lexemes -----------+-------------+------------------------+--------------+------------+-------------------------- - url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | {5aew.werc.ewr:8100/?xx} - host | Host | 5aew.werc.ewr:8100 | {simple} | simple | {5aew.werc.ewr:8100} - url_path | URL path | /?xx | {simple} | simple | {/?xx} + alias | description | token | dictionaries | configuration | command | lexemes +----------+-------------+------------------------+--------------+---------------+---------+-------------------------- + url | URL | 5aew.werc.ewr:8100/?xx | {simple} | simple | simple | {5aew.werc.ewr:8100/?xx} + host | Host | 5aew.werc.ewr:8100 | {simple} | simple | simple | {5aew.werc.ewr:8100} + url_path | URL path | /?xx | {simple} | simple | simple | {/?xx} (3 rows) SELECT token, alias, diff --git a/src/test/regress/sql/oidjoins.sql b/src/test/regress/sql/oidjoins.sql index 656cace..4e6730f 100644 --- a/src/test/regress/sql/oidjoins.sql +++ b/src/test/regress/sql/oidjoins.sql @@ -545,10 +545,6 @@ SELECT ctid, mapcfg FROM pg_catalog.pg_ts_config_map fk WHERE mapcfg != 0 AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_config pk WHERE pk.oid = fk.mapcfg); -SELECT ctid, mapdict -FROM pg_catalog.pg_ts_config_map fk -WHERE mapdict != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_ts_dict pk WHERE pk.oid = fk.mapdict); SELECT ctid, dictnamespace FROM pg_catalog.pg_ts_dict fk WHERE dictnamespace != 0 AND diff --git a/src/test/regress/sql/tsdicts.sql b/src/test/regress/sql/tsdicts.sql index 1633c0d..080ddc4 100644 --- a/src/test/regress/sql/tsdicts.sql +++ b/src/test/regress/sql/tsdicts.sql @@ -117,6 +117,57 @@ CREATE TEXT SEARCH DICTIONARY thesaurus ( SELECT ts_lexize('thesaurus', 'one'); +-- test dictionary pipeline in configuration +CREATE TEXT SEARCH CONFIGURATION english_union( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_union ALTER MAPPING FOR + asciiword + WITH english_stem UNION simple; + +SELECT to_tsvector('english_union', 'book'); +SELECT to_tsvector('english_union', 'books'); +SELECT to_tsvector('english_union', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_intersect( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_intersect ALTER MAPPING FOR + asciiword + WITH english_stem INTERSECT simple; + +SELECT to_tsvector('english_intersect', 'book'); +SELECT to_tsvector('english_intersect', 'books'); +SELECT to_tsvector('english_intersect', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_except( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_except ALTER MAPPING FOR + asciiword + WITH simple EXCEPT english_stem; + +SELECT to_tsvector('english_except', 'book'); +SELECT to_tsvector('english_except', 'books'); +SELECT to_tsvector('english_except', 'booking'); + +CREATE TEXT SEARCH CONFIGURATION english_branches( + COPY=english +); + +ALTER TEXT SEARCH CONFIGURATION english_branches ALTER MAPPING FOR + asciiword + WITH CASE ispell WHEN MATCH THEN KEEP + ELSE english_stem + END; + +SELECT to_tsvector('english_branches', 'book'); +SELECT to_tsvector('english_branches', 'books'); +SELECT to_tsvector('english_branches', 'booking'); + -- Test ispell dictionary in configuration CREATE TEXT SEARCH CONFIGURATION ispell_tst ( COPY=english @@ -189,6 +240,50 @@ SELECT to_tsvector('thesaurus_tst', 'one postgres one two one two three one'); SELECT to_tsvector('thesaurus_tst', 'Supernovae star is very new star and usually called supernovae (abbreviation SN)'); SELECT to_tsvector('thesaurus_tst', 'Booking tickets is looking like a booking a tickets'); +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN KEEP ELSE english_stem +END; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH thesaurus UNION english_stem; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH simple UNION thesaurus; +SELECT to_tsvector('thesaurus_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN MATCH THEN simple UNION thesaurus + ELSE simple +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two four'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING FOR asciiword WITH CASE + thesaurus WHEN NO MATCH THEN simple ELSE thesaurus +END; +\dF+ thesaurus_tst +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + +ALTER TEXT SEARCH CONFIGURATION thesaurus_tst ALTER MAPPING + REPLACE simple WITH english_stem; +SELECT to_tsvector('thesaurus_tst', 'one two'); +SELECT to_tsvector('thesaurus_tst', 'one two three'); +SELECT to_tsvector('thesaurus_tst', 'one two books'); + +CREATE TEXT SEARCH CONFIGURATION operators_tst ( + COPY=thesaurus_tst +); + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION simple; +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A'); + +ALTER TEXT SEARCH CONFIGURATION operators_tst ALTER MAPPING FOR asciiword WITH english_stem UNION (synonym, simple); +SELECT to_tsvector('operators_tst', 'The Mysterious Rings of Supernova 1987A Postgres'); + -- invalid: non-lowercase quoted identifiers CREATE TEXT SEARCH DICTIONARY tsdict_case ( diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index 637bfb3..26d771b 100644 --- a/src/test/regress/sql/tsearch.sql +++ b/src/test/regress/sql/tsearch.sql @@ -26,9 +26,9 @@ SELECT oid, cfgname FROM pg_ts_config WHERE cfgnamespace = 0 OR cfgowner = 0 OR cfgparser = 0; -SELECT mapcfg, maptokentype, mapseqno +SELECT mapcfg, maptokentype FROM pg_ts_config_map -WHERE mapcfg = 0 OR mapdict = 0; +WHERE mapcfg = 0; -- Look for pg_ts_config_map entries that aren't one of parser's token types SELECT * FROM ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
* [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms @ 2023-01-17 04:38 Andres Freund <[email protected]> 0 siblings, 0 replies; 265+ messages in thread From: Andres Freund @ 2023-01-17 04:38 UTC (permalink / raw) Until now we used struct timespec on all platforms but windows. Using struct timespe causes a fair bit of memory (struct timeval is 16 bytes) and runtime overhead (much more complicated additions). Instead we can convert the time to nanoseconds in INSTR_TIME_SET_CURRENT(), making the remaining operations cheaper. Representing time as int64 nanoseconds provides sufficient range, ~292 years relative to a starting point (depending on clock source, relative to the unix epoch or the system's boot time). That'd not be sufficient for calendar time stored on disk, but is plenty for runtime interval time measurement. On windows instr_time already is represented as cycles. It might make sense to represent time as cycles on other platforms as well, as using cycle acquisition instructions like rdtsc directly can reduce the overhead of time acquisition substantially. This could be done in a fairly localized manner as the code stands after this commit. Because the windows and non-windows paths are now more similar, use a common set of macros. To make that possible, most of the use of LARGE_INTEGER had to be removed, which looks nicer anyway. To avoid users of the API relying on the integer representation, we wrap the 64bit integer inside struct struct instr_time. Author: Andres Freund <[email protected]> Author: Lukas Fittl <[email protected]> Author: David Geier <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/portability/instr_time.h | 162 ++++++++++++++------------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h index 9ea1a68bd94..c0ed491395d 100644 --- a/src/include/portability/instr_time.h +++ b/src/include/portability/instr_time.h @@ -34,6 +34,8 @@ * * INSTR_TIME_GET_MICROSEC(t) convert t to uint64 (in microseconds) * + * INSTR_TIME_GET_NANOSEC(t) convert t to uint64 (in nanoseconds) + * * Note that INSTR_TIME_SUBTRACT and INSTR_TIME_ACCUM_DIFF convert * absolute times to intervals. The INSTR_TIME_GET_xxx operations are * only useful on intervals. @@ -54,8 +56,32 @@ #ifndef INSTR_TIME_H #define INSTR_TIME_H + +/* + * We store interval times as an int64 integer on all platforms, as int64 is + * cheap to add/subtract, the most common operation for instr_time. The + * acquisition of time and converting to specific units of time is platform + * specific. + * + * To avoid users of the API relying on the integer representation, we wrap + * the 64bit integer in a struct. + */ +typedef struct instr_time +{ + int64 ticks; /* in platforms specific unit */ +} instr_time; + + +/* helpers macros used in platform specific code below */ + +#define NS_PER_S INT64CONST(1000000000) +#define NS_PER_MS INT64CONST(1000000) +#define NS_PER_US INT64CONST(1000) + + #ifndef WIN32 + /* Use clock_gettime() */ #include <time.h> @@ -80,93 +106,43 @@ #define PG_INSTR_CLOCK CLOCK_REALTIME #endif -typedef struct timespec instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_clock_gettime_ns(void) +{ + instr_time now; + struct timespec tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).tv_nsec == 0 && (t).tv_sec == 0) + clock_gettime(PG_INSTR_CLOCK, &tmp); + now.ticks = tmp.tv_sec * NS_PER_S + tmp.tv_nsec; -#define INSTR_TIME_SET_ZERO(t) ((t).tv_sec = 0, (t).tv_nsec = 0) + return now; +} -#define INSTR_TIME_SET_CURRENT(t) ((void) clock_gettime(PG_INSTR_CLOCK, &(t))) +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_clock_gettime_ns()) -#define INSTR_TIME_ADD(x,y) \ - do { \ - (x).tv_sec += (y).tv_sec; \ - (x).tv_nsec += (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (t).ticks) -#define INSTR_TIME_SUBTRACT(x,y) \ - do { \ - (x).tv_sec -= (y).tv_sec; \ - (x).tv_nsec -= (y).tv_nsec; \ - /* Normalize */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - } while (0) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - do { \ - (x).tv_sec += (y).tv_sec - (z).tv_sec; \ - (x).tv_nsec += (y).tv_nsec - (z).tv_nsec; \ - /* Normalize after each add to avoid overflow/underflow of tv_nsec */ \ - while ((x).tv_nsec < 0) \ - { \ - (x).tv_nsec += 1000000000; \ - (x).tv_sec--; \ - } \ - while ((x).tv_nsec >= 1000000000) \ - { \ - (x).tv_nsec -= 1000000000; \ - (x).tv_sec++; \ - } \ - } while (0) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).tv_sec) + ((double) (t).tv_nsec) / 1000000000.0) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).tv_sec * 1000.0) + ((double) (t).tv_nsec) / 1000000.0) - -#define INSTR_TIME_GET_MICROSEC(t) \ - (((uint64) (t).tv_sec * (uint64) 1000000) + (uint64) ((t).tv_nsec / 1000)) #else /* WIN32 */ + /* Use QueryPerformanceCounter() */ -typedef LARGE_INTEGER instr_time; +/* helper for INSTR_TIME_SET_CURRENT */ +static inline instr_time +pg_query_performance_counter(void) +{ + instr_time now; + LARGE_INTEGER tmp; -#define INSTR_TIME_IS_ZERO(t) ((t).QuadPart == 0) + QueryPerformanceCounter(&tmp); + now.ticks = tmp.QuadPart; -#define INSTR_TIME_SET_ZERO(t) ((t).QuadPart = 0) - -#define INSTR_TIME_SET_CURRENT(t) QueryPerformanceCounter(&(t)) - -#define INSTR_TIME_ADD(x,y) \ - ((x).QuadPart += (y).QuadPart) - -#define INSTR_TIME_SUBTRACT(x,y) \ - ((x).QuadPart -= (y).QuadPart) - -#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ - ((x).QuadPart += (y).QuadPart - (z).QuadPart) - -#define INSTR_TIME_GET_DOUBLE(t) \ - (((double) (t).QuadPart) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MILLISEC(t) \ - (((double) (t).QuadPart * 1000.0) / GetTimerFrequency()) - -#define INSTR_TIME_GET_MICROSEC(t) \ - ((uint64) (((double) (t).QuadPart * 1000000.0) / GetTimerFrequency())) + return now; +} static inline double GetTimerFrequency(void) @@ -177,11 +153,45 @@ GetTimerFrequency(void) return (double) f.QuadPart; } +#define INSTR_TIME_SET_CURRENT(t) \ + ((t) = pg_query_performance_counter()) + +#define INSTR_TIME_GET_NANOSEC(t) \ + ((int64) (((double) (t).ticks * NS_PER_S) / GetTimerFrequency())) + #endif /* WIN32 */ -/* same macro on all platforms */ + +/* + * Common macros + */ + +#define INSTR_TIME_IS_ZERO(t) ((t).ticks == 0) + + +#define INSTR_TIME_SET_ZERO(t) ((t).ticks = 0) #define INSTR_TIME_SET_CURRENT_LAZY(t) \ (INSTR_TIME_IS_ZERO(t) ? INSTR_TIME_SET_CURRENT(t), true : false) + +#define INSTR_TIME_ADD(x,y) \ + ((x).ticks += (y).ticks) + +#define INSTR_TIME_SUBTRACT(x,y) \ + ((x).ticks -= (y).ticks) + +#define INSTR_TIME_ACCUM_DIFF(x,y,z) \ + ((x).ticks += (y).ticks - (z).ticks) + + +#define INSTR_TIME_GET_DOUBLE(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_S) + +#define INSTR_TIME_GET_MILLISEC(t) \ + ((double) INSTR_TIME_GET_NANOSEC(t) / NS_PER_MS) + +#define INSTR_TIME_GET_MICROSEC(t) \ + (INSTR_TIME_GET_NANOSEC(t) / NS_PER_US) + #endif /* INSTR_TIME_H */ -- 2.38.0 --vt42kbp2j7rjcapp Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0003-instr_time-Add-INSTR_TIME_SET_SECONDS-INSTR_TIME_.patch" ^ permalink raw reply [nested|flat] 265+ messages in thread
end of thread, other threads:[~2023-01-17 04:38 UTC | newest] Thread overview: 265+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-04-05 08:12 Re: Flexible configuration for full-text search Aleksandr Parfenov <[email protected]> 2018-04-05 14:26 ` Teodor Sigaev <[email protected]> 2018-04-05 18:37 ` Andres Freund <[email protected]> 2018-04-06 07:51 ` Aleksandr Parfenov <[email protected]> 2018-04-09 12:07 ` Aleksandr Parfenov <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]> 2023-01-17 04:38 [PATCH v8 2/5] instr_time: Represent time as an int64 on all platforms Andres Freund <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox