Wednesday, November 26, 2014
A working RAMDisk for PostgreSQL on Windows
Either PostgreSQL refuses to use them at all for datafiles or it seems to work, but at some point in time the server tells you, that there is something wrong with the datafiles on the ramdrive and data corruption bites.
Apparently most ramdrives on Windows provide drive emulations only good enough to support simple file storage.
ImDisk is the first I tried, that seems to work well with PostgreSQL.
It's free, has a GUI and a command line, and the driver is properly signed for x64 Windows.
Friday, November 7, 2014
There are people := {a,b} for a ≠ b IN (SELECT people FROM world): Those who understand SQL, and those who have to wait.
I just pushed a view on an Oracle Database from 464 seconds down to 3 seconds (~ 157x speedup or 0.0065% of the original execution time) by removing a few pointless ORDER BYs and replacing one expensive JOIN with subqueries.
Thursday, October 30, 2014
ToroDB Adventures: Adding unimplemented commands
> db.createCollection("test")
{ "ok" : 0, "errmsg" : "Unimplemented command: create", "code" : 1000002 }
to
> db.createCollection("test")
{ "ok" : 1 }
in five simple steps:
1.) Find out how the command is called internally. toroDB tells you that in the message:
"Unimplemented command: create"
so it is "create".
2.) Look up the "create" command. Start in QueryCommandProcessor.java with the QueryCommandGroup enum which contains all enums of all known commands. Since it is an administration command, you'll find it in AdministrationQueryCommand.java.
3.) "create" does nothing at the moment, so add some code.
I just copied it from "createIndexes" so
create,
becomes now
create {
@Override
public void doCall(RequestBaseMessage queryMessage, BSONDocument query, ProcessorCaller caller) throws Exception {
caller.create(query);
}
},
4.) Now, ProcessorCaller needs to know about the new command. It's enclosed in QueryCommandProcessor.java, so by adding
public void create(@Nonnull BSONDocument document) throws Exception {
queryCommandProcessor.create(document, messageReplier);
}
to ProcessorCaller AND the prototype
public void create(@Nonnull BSONDocument document, @Nonnull MessageReplier messageReplier) throws Exception;
to the enclosing QueryCommandProcessor class, make it known.
5.) Implement the command in ToroQueryCommandProcessor.java, which is the actual implementation of QueryCommandProcessor:
@Override
public void create(BSONDocument document, MessageReplier messageReplier) {
Map
keyValues.put("ok", MongoWP.OK);
String collectionName = (String) document.getValue("create");
Boolean capped = (Boolean) document.getValue("capped");
Boolean autoIndexId = (Boolean) document.getValue("autoIndexId");
Boolean usePowerOf2Sizes = (Boolean) document.getValue("usePowerOf2Sizes");
Double size = (Double) document.getValue("size");
Double max = (Double) document.getValue("max");
BSONDocument reply = new MongoBSONDocument(keyValues);
messageReplier.replyMessageNoCursor(reply);
}
And that's pretty much it. As of now it just reads all allowed values from the command and acknowledges OK. But now everything is set to make it a "real" command if needed.
Monday, October 27, 2014
The case for map/reduce with relational databases
I was at pgconf.eu and held a lightning talk about map/reduce with PostgreSQL. Upfront, I was asked "Why do you want to do that anyway?" and my initial response was like, "Because I can.". :-)
But that got me thinking about the real case behind the idea. What is the heart of map/reduce? Citing from the original paper:
"MapReduce is a programming model and an associated implementation for processing and generating large data sets." Note the word "sets"?
"Users specify a map function that processes a key/value pair to generate a set of intermediate key/value pairs, and a reduce function that merges all intermediate values associated with the same intermediate key.", or, expressed in a more formal way
map (k1,v1) -> list(k2,v2)
reduce (k2,list(v2)) -> list(v2)
Please note, that this is a bit more precise than the initial definition. The paper explains:
"I.e., the input keys and values are drawn from a different domain than the output keys and values. Furthermore, the intermediate keys and values are from the same domain as the output keys and values."
This is important, because the authors are introducing a domain transformation of the input data here. That is, in my opinion, already the heart of map/reduce.
Going back to the initial definition, this is basically what all RDBMS already do when processing parallel queries, be it by builtin ability or bolted on like with PL/Proxy + PostgreSQL: In the first step the input set is broken down to partitions, then the query runs on that partitions in parallel and produces intermediate result sets and finally that intermediate result sets are aggregated to the final result set. But the formal definition above adds a little twist, the domain transformation.
To clarify this, I'll use the canonical example, counting words in a text. The map function converts semi structured data, a text with lines of arbitrary length, into a well structured set of key (a word) and value (its count) tuples. This is the difference and the key to the power of map/redcue.
The ability to handle semi structured data which the relational model usually does not handle very well. (And I won't say unstructured data. Truly unstructured data is statistical noise.)
But modern RDBMS, especially PostgreSQL, often already have functions to transform semi structured data into relations and/or allow for user defined functions to extend their capabilities and that allows for running a map/reduce type job inside a RDBMS. Still, why would somebody want to do this?
1.) Integration
An awful lot of data is stored in relational models and will stay there. Simultaneously, especially for analytical workloads which become more and more important, the need for integrating relational and semi-structured data grows. Why handle them in different systems when one will do?
This decision of course heavily depends on the real world requirements. But rest assured that the datacenter guys who have to run the show will like to operate one database better than 2..n.
2.) Sets
Remember the word "sets" from the initial definition? And now the definition of a "relation" in a RDBMS:
"R is a relation on these n domains if it is a set of elements of the form (d1, d2, ..., dn) where dj ∈ Dj for each j=1,2,...,n." (E. F. Codd (Oct 1972). "Further normalization of the database relational model". "Data Base Systems". Courant Institute: Prentice-Hall. ISBN 013196741X.)
If a relation is a set of tuples with values from some domain D and map/reduce does domain transformation on key/value pairs (aka. tuples) what does that call for? Right, a very efficient set processor. Since relational DBMS are very efficient set processors by nature, they allow for
writing compact map/reduce functions that are also less error prone due to the declarative nature of SQL.
To clarify what I mean take a look at the following map and reduce functions for wordcount written for MongoDB in JavaScript from here:
var map = function() {
var summary = this.summary;
if (summary) {
// quick lowercase to normalize per your requirements
summary = summary.toLowerCase().split(" ");
for (var i = summary.length - 1; i >= 0; i--) {
// might want to remove punctuation, etc. here
if (summary[i]) { // make sure there's something
emit(summary[i], 1); // store a 1 for each word
}
}
}
};
var reduce = function( key, values ) {
var count = 0;
values.forEach(function(v) {
count +=v;
});
return count;
}
db.so.mapReduce(map, reduce, {out: "word_count"})
PostgreSQL:
For the code, see my previous post.
Well, while it seems to require more code than MongoDB, there is a subtle difference. The most PostgreSQL code is standard boilerplate to write a set returning function and to make PL/Proxy work. Once you got that right, you usually never have to look back. The actual work is done in two lines of SQL:
SELECT TRIM(both from word),count(1) FROM (SELECT regexp_split_to_table(line, E'\\W+') as word FROM kjb) w GROUP BY word
and
SELECT word,sum(count) FROM map_kjb() AS (word text, count bigint) WHERE word != '' GROUP BY word
IMHO, the ability to express this with extensively tried and tested functions instead of having to implement them yourself combined with strong typing is worth so much, that one should give it a try before looking somewhere else. Granted, raw performance may become so paramount that an alternative technology might be called for, but if you already use PostgreSQL now there is another reason to like and not to leave it.
And this is it, the long answer I didn't already had ready at the conference.
P.S. It also allows to move computation instead of data around.
Monday, October 20, 2014
pgconf.eu 2014
On the worker nodes:
CREATE TABLE kjb(
line text
);
CREATE OR REPLACE FUNCTION map_kjb()
RETURNS SETOF record AS
$BODY$
DECLARE r record;
BEGIN
FOR r IN (SELECT TRIM(both from word),count(1) FROM (SELECT regexp_split_to_table(line, E'\\W+') as word FROM kjb) w GROUP BY word) LOOP
RETURN NEXT r;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1000
ROWS 1000;
On the head node:
CREATE TYPE t_mr_wc AS (word text, count bigint);CREATE OR REPLACE FUNCTION map_kjb()
RETURNS SETOF record AS
$BODY$
CLUSTER 'head';
RUN ON ALL;
$BODY$
LANGUAGE plproxy VOLATILE
COST 1000
ROWS 1000;
CREATE OR REPLACE FUNCTION reduce_kjb()
RETURNS SETOF t_mr_wc AS
$BODY$ DECLARE result public.t_mr_wc; BEGIN
FOR result IN select word,sum(count) from map_kjb() AS (word text,count bigint) where word != '' group by word LOOP RETURN NEXT result; END LOOP; END; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 100;
1. Load data into tables.
2. Get word count:
SELECT * FROM reduce_kjb();
3. Get top 21 word counts:
SELECT * FROM reduce_kjb() ORDER BY 2 DESC LIMIT 21;
Wednesday, September 3, 2014
Using Code::Blocks with the Intel C/C++ compiler on Windows in five minutes
But there is an easier way:
Make two Intel compiler entries in Code::Blocks for the 32 and 64 bit version with the correct base directory, e.g. C:\Program Files (x86)\Intel\Composer XE\bin.
The 32 bit toolchain executables are prefixed ia32\, the 64 bit ones intel64\.
When you have installed the Intel compiler, there is an 'Command Prompt with Intel Compiler' entry in the start menu for 32 and 64 bit targets each. This opens a command prompt with all necessary paths and environment variables set correctly.
Just start codeblocks.exe from this command prompt and it inherits the environment. Then you can select the 32 or 64 bit Intel compiler option in the 'Project->Build options' and it works without further ado.
The only thing you cannot do is switching between 32 and 64 bit targets on the fly, you have to start Code::Blocks with the correct environment first.
Wednesday, April 16, 2014
NoSQL Matters Conference 2014, Cologne
Single Point of Entry: Integrating relational and semi-structured data with PostgreSQL
Thursday, March 20, 2014
Eclipse compiler produces faster FP code?
package the.plateisbad;
public class Simple {
public static void main(String[] args) {
final long arg_flops;
double i,x = 0.0d, y = 0.0d;
final long start, end;
if (args.length != 2) {
System.out.println("Usage: the.plateisbad.Simple
return;
}
arg_flops = Long.parseLong(args[0]);
y = Long.parseLong(args[1]);
System.out.println("Thinking really hard for " + arg_flops + " flops...");
start = System.currentTimeMillis();
for (i = 0; i < arg_flops; i++) {
x = i * y;
}
end = System.currentTimeMillis();
System.out.println("We calculated: " + x + " in " +(end-start)+ " ms");
}
}
I've stumbled over the fact, that it runs considerably faster when compiled with the Eclipse ECJ compiler compared to a standard javac.
With ECJ, executed with JDK 1.7:
java -server the.plateisbad.Simple 1000000000 3
Thinking really hard for 1000000000 flops...
We calculated: 2.999999997E9 in 1964 ms
With javac, executed with JDK 1.7:
java -server the.plateisbad.Simple 1000000000 3
Thinking really hard for 1000000000 flops...
We calculated: 2.999999997E9 in 3514 ms
With the new JDK 1.8, there is no noticeable difference between javac and ECJ:
java -server the.plateisbad.Simple 1000000000 3
Thinking really hard for 1000000000 flops...
We calculated: 2.999999997E9 in 3727 ms
but it is always the slowest of the three. The Bytecode tells me that ECJ builds a tail controlled loop which loops while i is < arg_flops:
64: invokestatic #52 // Method java/lang/System.currentTimeMillis:()J
67: lstore 9
69: dconst_0
70: dstore_3
71: goto 84
74: dload_3
75: dload 7
77: dmul
78: dstore 5
80: dload_3
81: dconst_1
82: dadd
83: dstore_3
84: dload_3
85: lload_1
86: l2d
87: dcmpg
88: iflt 74
91: invokestatic #52 // Method java/lang/System.currentTimeMillis:()J
while javac builds a head controlled loop that exits if i >= arg_flops:
67: invokestatic #13 // Method java/lang/System.currentTimeMillis:()J
70: lstore 9
72: dconst_0
73: dstore_3
74: dload_3
75: lload_1
76: l2d
77: dcmpg
78: ifge 94
81: dload_3
82: dload 7
84: dmul
85: dstore 5
87: dload_3
88: dconst_1
89: dadd
90: dstore_3
91: goto 74
94: invokestatic #13 // Method java/lang/System.currentTimeMillis:()J
And ECJ uses StringBuffer while javac uses StringBuilder for the String operations, but since these are not in the loop, that should not make any difference.
Does somebody know what is going on here?
UPDATE: This seems to be an anomaly. SciMark 2.0 shows now significant differences between ECJ and javac and jdk1.7 and jdk1.8 - with 1.8 being slightly faster.
Friday, March 7, 2014
Wednesday, March 5, 2014
A suggestion to all architects of high-security buildings ;->
Wednesday, February 12, 2014
Arbitrary parallel (well, almost) ad-hoc queries with PostgreSQL
PL/Proxy allows database partitioning and RUN ON ALL executes the function on all nodes simultaneously. PL/Proxy is limited to the partitioned execution of functions and has good reasons for this design. But PostgreSQL can execute dynamic SQL within functions, so let's see how far we can get.
Worker function (on all worker nodes):
CREATE OR REPLACE FUNCTION parallel_query(statement text)
RETURNS SETOF record AS
$BODY$
DECLARE r record;
BEGIN
IF lower($1) LIKE 'select%' THEN
FOR r IN EXECUTE $1 LOOP
RETURN NEXT r;
END LOOP;
ELSE
RAISE EXCEPTION 'Only queries allowed';
END IF;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Proxy function (on all head nodes):
CREATE OR REPLACE FUNCTION parallel_query(statement text)
RETURNS SETOF record AS
$BODY$
CLUSTER 'head'; RUN ON ALL;
$BODY$
LANGUAGE plproxy VOLATILE;
Table (on all worker nodes):
CREATE TABLE users
(
username text NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (username)
)
WITH (
OIDS=FALSE
);
With 10000 rows in two nodes, partitioned by username hash (~5000 on each node)
select * from parallel_query('select * from users') as (username text);
returns all 10000 rows. Since the nodes can be databases within the same server, there is no need for additional hardware, server installations etc. But if more performance is required in the future, adding more boxes is possible.
All it takes is logical partitioning and a bit of PL/pgSQL if you really need to run parallel queries.
There are some differences though. Take the following query:
select * from execute_query('select max(username) from users') as (username text);
"user_name_9995"
"user_name_9999"
It now returns two maximums, one for each partition. To get the expected result a second stage is needed:
select max(username) from execute_query('select max(username) from users') as (username text);
"user_name_9999"
The same applies for other aggregation functions like avg() etc.
The proxy function can finally be hidden in a VIEW:
CREATE OR REPLACE VIEW "users" AS select * from parallel_query('select * from users') as (username text);
Thursday, January 23, 2014
From palloc() to palloc0()
E.g.:
retval = (text *) palloc (len + VARHDRSZ);
memset(retval,0x0,len + VARHDRSZ);
becomes:
retval = (text *) palloc0 (len + VARHDRSZ);
The changes have been comitted to Github.
Tuesday, December 17, 2013
It compiles...
Short update: I have merged the two pull requests from Steffen Neumann and Björn Grüning into the repository and then made some minor corrections. LANGUAGE C (case insensitive but without the '') solves the one problem, replacing int4 with int32 the other. Actually this was no gcc issue, but the PostgreSQL guys have apparently decided to remove int4 as an PostgreSQL internal datatype from 9.2 to 9.3.
I had always wondered why there were so many duplicate but internally identical (e.g. int4 (number of bytes) and int32 (number of bits)) datatypes anyway. Probably some legacy...
Also, pgchem_tigress can now be installed as an relocatable extension with the CREATE EXTENSION mechanism. No need to run various installation scripts anymore (but still supported).
Thursday, October 24, 2013
Natural vs. Chemical
Maybe it would help all the people who like to make a firm distinction between 'natural products' -> good and 'chemical products' -> bad to attend such a lecture from time to time...
Wednesday, September 18, 2013
Long time no see
the development of pgchem::tigress - and this Blog - have been quiet for some time now.
Professionally, I'm currently moving into two new spheres of activity, document management for product registration (i.e. license to sell) and bioinformatics, and this occupies most of my time. That my wife had to go to the hospital for a few months (nothing life threatening, but quite hindering) didn't help much either. ;->
But, since Postgresql 9.3 just came out, I'll see if I can manage at least to compile pgchem::tigress against 9.3, OpenBabel 2.3.2 and Indigo 1.1.11 before 2013 ends...
Friday, December 23, 2011
Christmas presents
Jérôme Pansanel has completed the new serialization code for mychem and pgchem, so there is no need to handle stereo and non-stereo queries differently anymore. I have moved the index functions to GCC's vector extensions where applicable, and the first result is that index build times have been roughly cut by half while substructure search times have improved, but not that much.
Index build times
| System | Index build time |
|---|---|
| pgchem with OpenBabel or Indigo | 352137 ms |
| pgchem with OpenBabel or Indigo vectorized | 192815 ms |
OpenBabel with binary storage and FP2 fingerprint vectorized
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH2 | 4840 | 98416 ms | 4840 | 17044 ms |
| GH7 | 260 | 94053 ms | 260 | 1564 ms |
| GH13 | 580 | 113690 ms | 580 | 34504 ms |
| GH16 | 26910 | 99365 ms | 26910 | 55154 ms |
Merry Christmas and a happy new year!
Saturday, December 17, 2011
Benchmark data published
Thursday, December 15, 2011
Selected GH17 results for 10^6 structures
GH17 substructure search speed
OpenBabel with binary+SMILES storage and FP2 fingerprint
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH2 | 4840 | 108807 ms | 4840 | 21164 ms |
| GH7 | 260 | 105050 ms | 260 | 1934 ms |
| GH13 | 580 | 118978 ms | 580 | 52416 ms |
| GH16 | 26910 | 109886 ms | 26910 | 64742 ms |
Indigo with binary storage and ext+sub fingerprint
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH2 | 4840 | 213075 ms | 4840 | 27887 ms |
| GH7 | 410 | 178963 ms | 410 | 4451 ms |
| GH13 | 580 | 251938 ms | 580 | 39134 ms |
| GH16 | 27100 | 172534 ms | 27100 | 80523 ms |
Bingo 1.7beta2 with molfiles as text storage
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH2 | 4710 | 647889 ms | 4710 | 21733 ms |
| GH7 | 410 | 538784 ms | 410 | 6658 ms |
| GH13 | 580 | 675093 ms | 580 | 12418 ms |
| GH16 | 27100 | 528891 ms | 27100 | 28541 ms |
Index build times
| System | Index build time |
|---|---|
| pgchem with OpenBabel or Indigo | 352137 ms |
| Bingo | 3458681ms |
Again, Bingo without it's index is apparently killed by the overhead of parsing text into the internal molecule format. With index it's a mixed bag, while it shines at GH13 and GH16, pgchem is about equal or faster at GH2 and GH7.
Wednesday, December 14, 2011
GH17 results
Mikhail Rybalkin from GGA Software asked me to do this, so here it is...
The GH17 test queries used
There is a small set of queries used in article Chemical substructure search in SQL by Golovin and Henrick. These queries were lately reused in other articles:
GH1 ONC1CC(C(O)C1O)[n]2cnc3c(NC4CC4)ncnc23
GH2 Nc1ncnc2[n]cnc12
GH3 CNc1ncnc2[n](C)cnc12
GH4 Nc1ncnc2[n](cnc12)C3CCCC3
GH5 CC12CCC3C(CCC4=CC(O)CCC34C)C1CCC2
GH6 OC2=CC(=O)c1c(cccc1)O2
GH7 Nc1nnc(S)s1
GH8 C1C2SCCN2C1
GH9 CP(O)(O)=O
GH10 CCCCCP(O)(O)=O
GH11 N2CCC13CCCCC1C2Cc4c3cccc4
GH12 s1cncc1
GH13 C34CCC1C(CCC2CC(=O)CCC12)C3CCC4
GH14 CCCCCCCCCCCP(O)(O)=O
GH15 CC1CCCC1
GH16 CCC1CCCC1
GH17 CCCC1CCCC1
GH17 substructure search speed
OpenBabel with binary+SMILES storage and FP2 fingerprint
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH1 | 0 | 9517 ms | 0 | 25 ms |
| GH2 | 484 | 8519 ms | 484 | 111 ms |
| GH3 | 63 | 8632 ms | 63 | 43 ms |
| GH4 | 5 | 8950 ms | 5 | 48 ms |
| GH5 | 36 | 10020 ms | 36 | 78 ms |
| GH6 | 0 | 8696 ms | 0 | 32 ms |
| GH7 | 26 | 8279 ms | 26 | 31 ms |
| GH8 | 170 | 8454 ms | 170 | 56 ms |
| GH9 | 348 | 8068 ms | 348 | 71 ms |
| GH10 | 36 | 8820 ms | 36 | 21 ms |
| GH11 | 66 | 9113 ms | 66 | 52 ms |
| GH12 | 831 | 7920 ms | 831 | 124 ms |
| GH13 | 58 | 9864 ms | 58 | 448 ms |
| GH14 | 4 | 9998 ms | 4 | 36 ms |
| GH15 | 3008 | 8549 ms | 3008 | 555 ms |
| GH16 | 2691 | 8665 ms | 2691 | 501 ms |
| GH17 | 2290 | 8717 ms | 2290 | 560 ms |
Indigo with binary storage and ext+sub fingerprint
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH1 | 0 | 28161 ms | 0 | 32 ms |
| GH2 | 484 | 19188 ms | 484 | 187 ms |
| GH3 | 68 | 20498 ms | 68 | 78 ms |
| GH4 | 5 | 23388 ms | 5 | 33 ms |
| GH5 | 36 | 25887 ms | 36 | 62 ms |
| GH6 | 0 | 21034 ms | 0 | 31 ms |
| GH7 | 41 | 16302 ms | 41 | 31 ms |
| GH8 | 170 | 16629 ms | 170 | 78 ms |
| GH9 | 373 | 14005 ms | 373 | 98 ms |
| GH10 | 37 | 16881 ms | 37 | 21 ms |
| GH11 | 66 | 24091 ms | 66 | 78 ms |
| GH12 | 829 | 14842 ms | 829 | 210 ms |
| GH13 | 58 | 24470 ms | 58 | 133 ms |
| GH14 | 4 | 21403 ms | 4 | 36 ms |
| GH15 | 3047 | 15817 ms | 3047 | 749 ms |
| GH16 | 2710 | 16767 ms | 2710 | 732 ms |
| GH17 | 2304 | 17524 ms | 2304 | 788 ms |
Bingo 1.7beta2 with molfiles as text storage
| Query | Hits | no Index | Hits | with Index |
|---|---|---|---|---|
| GH1 | 0 | 70277 ms | 0 | 125 ms |
| GH2 | 471 | 56821 ms | 471 | 156 ms |
| GH3 | 68 | 57754 ms | 68 | 125 ms |
| GH4 | 5 | 60067 ms | 5 | 125 ms |
| GH5 | 36 | 65586 ms | 36 | 140 ms |
| GH6 | 79 | 57188 ms | 79 | 125 ms |
| GH7 | 41 | 52134 ms | 41 | 125 ms |
| GH8 | 170 | 51685 ms | 170 | 140 ms |
| GH9 | 373 | 47613 ms | 373 | 138 ms |
| GH10 | 37 | 49961 ms | 37 | 110 ms |
| GH11 | 66 | 61176 ms | 66 | 125 ms |
| GH12 | 774 | 50281 ms | 829 | 156 ms |
| GH13 | 58 | 61108 ms | 58 | 156 ms |
| GH14 | 4 | 53636 ms | 4 | 140 ms |
| GH15 | 3047 | 50213 ms | 3047 | 343 ms |
| GH16 | 2710 | 51227 ms | 2710 | 327 ms |
| GH17 | 2304 | 51495 ms | 2304 | 362 ms |
Fingerprint efficiency (with regard to false positives)
FP2
| Query | Candidates screened | Hits matched | false positives | Efficiency |
|---|---|---|---|---|
| GH1 | 0 | 0 | 0 | 1.000 |
| GH2 | 485 | 484 | 1 | 0.998 |
| GH3 | 69 | 63 | 6 | 0.913 |
| GH4 | 37 | 5 | 32 | 0.135 |
| GH5 | 120 | 36 | 84 | 0.300 |
| GH6 | 79 | 0 | 79 | 0.000 |
| GH7 | 41 | 26 | 15 | 0.634 |
| GH8 | 177 | 170 | 7 | 0.960 |
| GH9 | 377 | 348 | 29 | 0.923 |
| GH10 | 37 | 36 | 1 | 0.973 |
| GH11 | 123 | 66 | 1 | 0.537 |
| GH12 | 831 | 831 | 0 | 1.000 |
| GH13 | 1346 | 58 | 1288 | 0.043 |
| GH14 | 20 | 4 | 16 | 0.200 |
| GH15 | 3760 | 3008 | 752 | 0.800 |
| GH16 | 3305 | 2691 | 614 | 0.814 |
| GH17 | 3305 | 2290 | 715 | 0.762 |
ext+sub
| Query | Candidates screened | Hits matched | false positives | Efficiency |
|---|---|---|---|---|
| GH1 | 0 | 0 | 0 | 1.000 |
| GH2 | 484 | 484 | 1 | 1.000 |
| GH3 | 68 | 68 | 0 | 1.000 |
| GH4 | 5 | 5 | 0 | 1.000 |
| GH5 | 47 | 36 | 11 | 0.766 |
| GH6 | 0 | 0 | 0 | 1.000 |
| GH7 | 41 | 41 | 0 | 1.000 |
| GH8 | 170 | 170 | 0 | 1.000 |
| GH9 | 373 | 373 | 0 | 1.000 |
| GH10 | 37 | 37 | 0 | 1.000 |
| GH11 | 66 | 66 | 0 | 1.000 |
| GH12 | 829 | 829 | 0 | 1.000 |
| GH13 | 259 | 58 | 201 | 0.224 |
| GH14 | 20 | 4 | 16 | 0.200 |
| GH15 | 3061 | 3047 | 14 | 0.995 |
| GH16 | 2720 | 2710 | 10 | 0.996 |
| GH17 | 2720 | 2304 | 416 | 0.847 |
Index build times
| System | Index build time |
|---|---|
| pgchem with OpenBabel or Indigo | 25690 ms |
| Bingo | 336319 ms |
Indigo's ext+sub fingerprint is truly more selective than FP2. Still, OpenBabel with binary storage shows the better prformance because of its
Also, the result for GH3, GH7, GH9, GH12, GH15, GH16, and GH17 are different between OpenBabel and Indigo, and Bingo finds 79 hits for GH6 where pgchem finds zero.
Index building on pgchem is 13 times faster than Bingo, but since pgchem (currently) does not support features like tautomer searching or SMARTS searching with index support this comparison is a bit like apples and oranges.
The slow performance of Bingo without index, comparable to pgchem without binary storage, is quite likely a result of the storage of molecules in textual representation. Parsing text to binary molecules is a first class performance killer. Unfortunately, there is no way to convert molecules into native format directly with Bingo for PostgreSQL, but Bingo does the conversion implicitly when building the index.