Multi-threaded use of RGroupDecomposition class

User 22337819af

30-01-2012 21:44:26

Hi


I have written a multi-threaded application to carry out r-group decomposition.  I have a set of cores/query molecules (~30) that I am running against a library of structures (~100000).  For each thread is assigned 1/n (n = # threads) of the structures, I create a Molecule object for each core, an RGroupDecomposition object, and then iterate over the assigned structures, creating a Molecule object for one, running the r-group decomposition, then storing the result in memory.


The performance is about half of what we expect it to be.  We have an "8 threaded" intel CORE i& vPro chip, which (with other code) we can get gains as we increase the number of threads to 8.  With the r-group decomposition, we only get gains increasing to 4 threads.  When we run an analyzer (YourKit) we observe lots of blocking on the threads.  We have identified these classes/methods (from a snapshot) as being possibly responsible for the blocking:


chemaxon.enumeration.QueryEnumeratorFactory.getFirstEnumeratorClone(Molecule, List, int, boolean, String)


chemaxon.enumeration.SearchEnumerator.getMarkushFactory(int)


chemaxon.enumeration.getQueryFactory(int)


chemaxon.license.LicenseHandler.checkLicense(String, String)


java.util.LinkedHashMap.newEntryIterator()


 


code:


 


final Map<Core, Molecule> coreMoleculeMap;


try {


coreMoleculeMap = buildCoreMoleculeMap(coreSet);


} catch (MolFormatException e2) {


e2.printStackTrace();


throw new RuntimeException("", e2);


}



Standardizer standardizer;


try {


standardizer = new Standardizer("aromatize..clearstereo:chirality");


} catch (StandardizerException e1) {


e1.printStackTrace();


throw new RuntimeException("", e1);


}



RGroupDecomposition rGroupDecomp = new RGroupDecomposition();


rGroupDecomp.setAttachmentType(RGroupDecomposition.ATTACHMENT_RLABEL);


Stereochemistry stereoChemistry = new Stereochemistry();


 


int structureProgress = 0;


for (Structure curStruct : structureList) {


Molecule curMol;


try {


curMol = MolImporter.importMol(curStruct.getSmiles());


} catch (MolFormatException e1) {


e1.printStackTrace();


throw new RuntimeException("", e1);


}


 


 


Molecule curWithoutStereoMol = curMol.cloneMolecule();


try {


standardizer.standardize(curWithoutStereoMol);


} catch (SearchException e1) {


e1.printStackTrace();


throw new RuntimeException("", e1);


} catch (LicenseException e1) {


e1.printStackTrace();


throw new RuntimeException("", e1);


}


 


 


rGroupDecomp.setTarget(curWithoutStereoMol);



for (Core core : coreSet) {


Molecule coreMol = coreMoleculeMap.get(core);


rGroupDecomp.setQuery(coreMol);


 


final Decomposition rDecomp = getFirstDecompAndHandleErrors(rGroupDecomp);



if (rDecomp != null) { //assuming that decomp returns null if no match found


if (curStruct.getCore() != null) {


System.err.println("more than one match found for structure " + curStruct.getBroadCoreId() + " " 


+ curStruct.getCollectionName() + " " + curStruct.getSmiles() + " " + curStruct.getCore().getCxSmiles() + " " + core.getCxSmiles());


//warning / error - a previous match was found with a previous core 


//and this molecule


} else {


curStruct.setCore(core);


for (Molecule ligandMol : rDecomp.getLigands()) {


if (ligandMol != null) {


curStruct.addRGroup(findRIndexForLigand(ligandMol), ligandMol);


}


}


 


 


int[][] groupHit = rDecomp.getGroupHit();


if (groupHit[0].length != 1) {


//warning / error either more than one hit, or no hit was found


}


 


stereoChemistry.setMolecule(coreMol);


for (int coreAsymmIndex : core.getStereocenterAtomIndexSet()) {


final int structAsymmIndex = groupHit[coreAsymmIndex][0];


final String rsVal;


if (StereoConstants.CHIRALITY_R == curMol.getChirality(structAsymmIndex)) {


rsVal = "R";


} else if (StereoConstants.CHIRALITY_S == curMol.getChirality(structAsymmIndex)) {


rsVal = "S";


} else {


throw new RuntimeException("RGroupDecomposition chirality is neither R or S for asymmetric atom " + structAsymmIndex + " of molecule " + curStruct.getSmiles());


}


curStruct.addStereocenterConfig(coreAsymmIndex, rsVal);


}



if (mixComp != null) {


handleMixture(mixComp, curStruct, rGroupDecomp, groupHit, coreMol, curMol);


}


}


}




}



if (curStruct.getCore() == null) {


System.err.println(threadName + " No matching core found for " + curStruct.getBroadCoreId() + " " + curStruct.getSmiles());


}


if (structureProgress % 1000 == 0) {


System.out.println(threadName + " progress " + structureProgress);


}


structureProgress++;


}


 


Edit:  attached screenshot of profile showing extensive blocking during multi-thread operation

ChemAxon fb166edcbd

31-01-2012 16:28:06

Thanks for the report. RGroupDecomposition was not designed for concurrent environments, and the blocker methods you picked out are mainly static synchronized methods for safety reasons, which are known to slow down concurrent processing.


We suggest that you should use some sort of database to store this huge number of structures. Then you can use JChemSearch which is automatically multithreaded. Standardization is done during DB import, this is another gain in speed. We have JChemSearch.getHitsAsRgDecomp() to get the search results in form of R-group decomposition.


For further details, refer to the Retrieve search results section in our  developer's guide.


To set up a database, see our connection guide. If you are not familiar with databases, we suggest to use Apache Derby which is a simple file based database with simple configuration.

User 22337819af

31-01-2012 21:11:36

Thakns for the quick response.   Would it be possible to add this information to the documentation (API) of the RGroupDecomposition class?  That class appeared to meet my needs perfectly, so I had no idea I should continue searching.  I never came across the option of using JChemSearch and converting the result to an R-group decomposition, or the information about multi-threading.


We do have our structures in a database already, but doing standardization during DB import would not be an option for us.  We are using the standardization only during this calculation to temporarily remove the stereocenter configurations.  After the match is found, we then use the query-target matching (hit) to determine the stereocenter configurations of the target at stereocenters of interest of the original molecule.


We plan on investigating what gains in speed we can get from using JChemSearch as you have described, but as another potential workaround we might create N separate processes and have our R-group decomposition work divided up among them.  We are guessing that this would eliminate the blocking issue, is that correct?

ChemAxon fb166edcbd

01-02-2012 16:44:26

Yes, you are right that this is not written in the RGroupDecomposition docs, we will supplement the docs with this info.


Yes, if you use the RGroupDecomposition objects in different JVM-s then they will not block each other, this may be a plausible solution.

User 22337819af

01-02-2012 22:22:34

Thank you, it will be good to have that as part of the API.

ChemAxon fb166edcbd

02-02-2012 16:18:08

One more remark on standardization: if you only use it in order to ignore stereo info during search, then you can just as well use SearchOptions.setStereoSearchType(int) to set this (SearchConstants.STEREO_IGNORE).


You can get the SearchOptions object by JChemSearch.getSearchOptions() or MolSearch.getSearchOptions() (for RGroupDecomposition). For example, you can write


RGroupDecomposition rgd = new RGroupDecomposition();
rgd.getSearchOptions().setStereoSearchType(SearchConstants.STEREO_IGNORE);

If you have further experience with any of the concurrent RGroupDecomposition usages (separate JVM-s or JChemSearch API) then please give us feedback about your findings.

User 22337819af

28-02-2012 15:37:28

Just a quick update - we have done prototype work to use the JChemSearch to carry out the R-group decomposition, and it appears to faster by roughly a factor of 2.  Now we need to re-write our code to use the different classes / objects returned by JChemSearch instead of the RGroupDecomposition class.


When we monitor the processor usage for the above we noted that each thread / core on the processor was only getting used at ~15%.  Also, we were wondering how much load was placed on the database by this process?


Another note, when running 2 threads with RGroupDecomposition there appeared to be minimal / no blocking (intel processor).  3 threads began to have some blocking but still be efficient, while adding a 4th thread provided some, but not much, benefit.


Thanks again for the suggestion.

User 22337819af

29-02-2012 19:18:19

We need to get the GroupHit information that was available from the Decomposition, I started a separate thread for that question becaues I thought it might be generally useful:


https://www.chemaxon.com/forum/viewpost40535.html#40535