Simple Go vs Java performance comparison in one particular application - java

I create simple test which compare my Go and Java application performance
I do not know why but it looks like my Java application is faster than Go
I used:
~> go version
go version go1.15.6 darwin/amd64
and
~> java -version
openjdk version "15.0.1" 2020-10-20
OpenJDK Runtime Environment (build 15.0.1+9)
OpenJDK 64-Bit Server VM (build 15.0.1+9, mixed mode, sharing)
Go function mostly tested is:
func split(text string, occurrence map[string]int, separators []string) {
words := strings.Split(text, separators[0])
for _, w := range words {
if len(w) > 0 {
if len(separators) > 1 {
split(w, occurrence, separators[1:])
} else {
occurrence[w] = occurrence[w] + 1
}
}
}
}
Java equivalent:
private void split(String text, Map<String, Integer> occurrence, String[] separators) {
StringTokenizer st = new StringTokenizer(text, separators[0]);
while (st.hasMoreTokens()) {
if (separators.length > 1) {
split(st.nextToken(), occurrence, Arrays.copyOfRange(separators, 1, separators.length));
} else {
occurrence.compute(st.nextToken(),(k,v) -> v == null ? 1 : v+1);
}
}
}
Start 10 threads and execute this method against text loaded from ulyss10.txt file in the loop (text is loaded into memory once on the beginning of application execution - it is not I/O test).
There you can see all files from test: https://github.com/TOlchawa/go-vs-java/tree/main/book_read_test
My expectation was Go will be faster - but results are opposite.
It looks like Go is little bit slower - about: 40% slower (which is unexpected)
I know this is not a very reliable test - but nevertheless I'm surprised.
Could you provide me list of possible reasons why it was happen, please?
in my understanding it is difference between:
strings.Split | StringTokenizer
slider | HashMap
routine | Thread
go compiler | JVM
memory management | GC
differences in source code of application (IMHO it is not an issue)
what else ?
//edit
There was a wrong version in Github repo - but during my tests I used correct and the question is still valid/open.

Rather than doing a game of guessing. Here is what I would do to understand what happened.
Run a program profiling for both java and golang. Visualise them into memory allocation flow and flame graph.
See where each program spend most of their time.
Both golang and java has the same runtime speed for more/less equivalent low-level task that they do.
However, they underlying implementation for strings library can be different and hence lead to very huge performance difference.
For example, have you give a thought on how Golang vs Java implement a map? How about string splitting.
Any excessive bytes copy operations?
In real-world, performance optimisation are mostly cpu and memory management, not the "flashy" better big O algorithms. See Kafka vs RMQ. A lot of the performance edge came from better socket buffer management and zero-copy technique, which isn't rocket science algo at all.

Related

How to use Stanford CoreNLP java library with Ruby for sentiment analysis?

I'm trying to do sentiment analysis on a large corpus of tweets in a local MongoDB instance with Ruby on Rails 4, Ruby 2.1.2 and Mongoid ORM.
I've used the freely available https://loudelement-free-natural-language-processing-service.p.mashape.com API on Mashape.com, however it starts timing out after pushing through a few hundred tweets in rapid fire sequence -- clearly it isn't meant for going through tens of thousands of tweets and that's understandable.
So next I thought I'd use the Stanford CoreNLP library promoted here: http://nlp.stanford.edu/sentiment/code.html
The default usage, in addition to using the library in Java 1.8 code, seems to be to use XML input and output files. For my use case this is annoying given I have tens of thousands of short tweets as opposed to long text files. I would want to use CoreNLP like a method and do a tweets.each type of loop.
I guess one way would be to construct an XML file with all of the tweets and then get one out of the Java process and parse that and put it back to the DB, but that feels alien to me and would be a lot of work.
So, I was happy to find on the site linked above a way to run CoreNLP from the command line and accept the text as stdin so that I didn't have to start fiddling with the filesystem but rather feed the text as a parameter. However, starting up the JVM separately for each tweet adds a huge overhead compared to using the loudelement free sentiment analysis API.
Now, the code I wrote is ugly and slow but it works. Still, I'm wondering if there's a better way to run the CoreNLP java program from within Ruby without having to start fiddling with the filesystem (creating temp files and giving them as params) or writing Java code?
Here's the code I'm using:
def self.mass_analyze_w_corenlp # batch run the method in multiple Ruby processes
todo = Tweet.all.exists(corenlp_sentiment: false).limit(5000).sort(follow_ratio: -1) # start with the "least spammy" tweets based on follow ratio
counter = 0
todo.each do |tweet|
counter = counter+1
fork {tweet.analyze_sentiment_w_corenlp} # run the analysis in a separate Ruby process
if counter >= 5 # when five concurrent processes are running, wait until they finish to preserve memory
Process.waitall
counter = 0
end
end
end
def analyze_sentiment_w_corenlp # run the sentiment analysis for each tweet object
text_to_be_analyzed = self.text.gsub("'"){" "}.gsub('"'){' '} # fetch the text field of DB item strip quotes that confuse the command line
start = "echo '"
finish = "' | java -cp 'vendor/corenlp/*' -mx250m edu.stanford.nlp.sentiment.SentimentPipeline -stdin"
command_string = start+text_to_be_analyzed+finish # assemble the command for the command line usage below
output =`#{command_string}` # run the CoreNLP on the command line, equivalent to system('...')
to_db = output.gsub(/\s+/, "").downcase # since CoreNLP uses indentation, remove unnecessary whitespace
# output is in the format of "neutral, "positive", "negative" and so on
puts "Sentiment analysis successful, sentiment is: #{to_db} for tweet #{text_to_be_analyzed}."
self.corenlp_sentiment = to_db # insert result as a field to the object
self.save! # sentiment analysis done!
end
You can at least avoid the ugly and dangerous command line stuff by using IO.popen to open and communicate with the external process, for example:
input_string = "
foo
bar
baz
"
output_string =
IO.popen("grep 'foo'", 'r+') do |pipe|
pipe.write(input_string)
pipe.close_write
pipe.read
end
puts "grep said #{output_string.strip} but not bar"
EDIT: to avoid the overhead of reloading the Java program on each item, you can open the pipe around the todo.each loop an communicate with the process like this
inputs = ['a', 'b', 'c', 'd']
IO.popen('cat', 'r+') do |pipe|
inputs.each do |s|
pipe.write(s + "\n")
out = pipe.readline
puts "cat said '#{out.strip}'"
end
end
that is, if the Java program supports such line-buffered "batch" input. However, it should not be very difficult to modify it to do so, if not.
As suggested in the comments by #Qualtagh, I decided to use JRuby.
I first attempted to use Java to use MongoDB as the interface (read directly from MongoDB, analyze with Java / CoreNLP and write back to MongoDB), but the MongoDB Java Driver was more complex to use than the Mongoid ORM I use with Ruby, so this is why I felt JRuby was more appropriate.
Doing a REST service for Java would have required me first to learn how to do a REST service in Java, which might have been easy, or then not. I didn't want to spend time figuring that out.
So the code I needed to do to run my code was:
def analyze_tweet_with_corenlp_jruby
require 'java'
require 'vendor/CoreNLPTest2.jar' # I made this Java JAR with IntelliJ IDEA that includes both CoreNLP and my initialization class
analyzer = com.me.Analyzer.new # this is the Java class I made for running the CoreNLP analysis, it initializes the CoreNLP with the correct annotations etc.
result = analyzer.analyzeTweet(self.text) # self.text is where the text-to-be-analyzed resides
self.corenlp_sentiment = result # adds the result into this field in the MongoDB model
self.save!
return "#{result}: #{self.text}" # for debugging purposes
end

Java Object memory usage - ibm jvm 1.4.2

Is it possible to find memory usage of object in java within application?
I want to have object memory usage to be part of debug output when application runs.
I don't want to connect using external application to VM.
I have a problem that few classes eats up huge amount of memory and causes memory
problems, my app gets crash. I need to find that memory usage (I am working with limited memory resources).
EDIT: I am using java 1.4:/
See my pet project, MemoryMeasurer. A tiny example:
long memory = MemoryMeasurer.measureBytes(new HashMap());
You may also derive more qualitative memory breakdown:
Footprint footprint = ObjectGraphMeasurer.measure(new HashMap());
For example, I used the latter to derive the per entry cost of various data structures, where the overhead is measured in number of objects created, references, and primitives, instead of just bytes (which is also doable). So, next time you use a (default) HashSet, you can be informed that each element in it costs 1 new object (not your element), 5 references, and an int, which is the exact same cost for an entry in HashMap (not unexpectedly, since any HashSet element ends up in a HashMap), and so on.
You can use it on any object graph. If your object graph contains links other structures you do wish to ignore, you should use a predicate to avoid exploring them.
Edit Instrumentation is not available to Java 1.4 (wow, people still use that?!), so the memoryBytes call above wouldn't work for you. But the second would. Then you can write something like this (if you are on a 32bit machine):
long memory = footprint.getObjects() * 8 + footprint.getReferences() * 4 +
footprint.getPrimitives().count(int.class) * 4 +
footprint.getPrimitives().count(long.class) * 8 + ...;
That gives you an approximation. A better answer would be to ceil this to the closest multiple of 16:
long alignedMemory = (x + 15) & (~0xF); //the last part zeros the lowest 4 bits
But the answer might still be off, since if you find, say, 16 booleans, it's one thing if they are found in the same object, and quite another if they are spread in multiple objects (and cause excessive space usage due to aligning). This logic could be implemented as another visitor (similar to how MemoryMeasurer and ObjectGraphMeasurer are implemented - quite simply as you may see), but I didn't bother, since that's what Instrumentation does, so it would only make sense of Java versions below 1.5.
Eclipse MAT is a really good tool to analyze memory.
There are tools that comes with jdk such as jmap and jhat which provides object level details.
The folowing link provides a piece of Java Code computing the size of objects:
http://www.javaworld.com/javaworld/javatips/jw-javatip130.html

Findbugs and comparing

I recently started using the findbugs static analysis tool in a java build I was doing. The first report came back with loads of High Priority warnings. Being the obsessive type of person, I was ready to go knock them all out. However, I must be missing something. I get most of the warnings when comparing things. Such as the following code:
public void setSpacesPerLevel(int value)
{
if( value >= 0)
{
spacesPerLevel = value;
}
else
{
spacesPerLevel = 0;
}
}
produces a high priority warning at the if statement that reads.
File: Indenter.java, Line: 60, Type:
BIT_AND_ZZ, Priority: High, Category:
CORRECTNESS Check to see if ((...) &
0) == 0 in
sample.Indenter.setSpacesPerLevel(int)
I am comparing an int to an int, seems like a common thing. I get quite a few of that type of error with similar simple comparisons.
I have alot of other high priority warnings on what appears to be simple code blocks. Am I missing something here? I realize that static analysis can produce false positives, but the errors I am seeing seem too trivial of a case to be a false positive.
This one has me scratching my head as well.
for(int spaces = 0;spaces < spacesPerLevel;spaces++)
{
result = result.concat(" ");
}
Which gives the following findbugs warning:
File: Indenter.java, Line: 160, Type: IL_INFINITE_LOOP, Priority: High, Category: CORRECTNESS
There is an apparent infinite loop in sample.Indenter.indent()
This loop doesn't seem to have a way to terminate (other than by perhaps throwing an exception).
Any ideas?
So basically I have a handful of files and 50-60 high priority warnings similar to the ones above. I am using findbugs 1.3.9 and calling it from the findbugs ant task
UPDATE:
I have this build being executed by a hudson server and had the code being instrumented by Clover for code coverage. When I turned that off, all of my high priority warnings disappeared. That makes sense now. Thanks for the feedback.
UPDATE: I have this build being executed by a hudson server and had the code being instrumented by Clover for code coverage. When I turned that off, all of my high priority warnings disappeared. That makes sense now. Thanks for the feedback.
A side note:
for(int spaces = 0;spaces < spacesPerLevel;spaces++)
{
result = result.concat(" ");
}
If result is a java.lang.String, this may be inefficient, as you do the following steps for each space character:
create a new char[] to hold the result of the concatenation
create a new java.lang.String instance that is wrapped around the character array
If you do this repeatedly, especially when result is already long, this takes a lot of time.
If performance (both time and memory) is important for that method, you should consider using a StringBuilder (not thread-safe) or a StringBuffer (thread-safe).
Are you running Findbugs thru Eclipse plugin, ant or gui? is it possible that the code hasn't recompiled since you ran it (before making changes)?
if setSpacesPerLevel isn't too long, post the output of
javap -v TheClassThatContainssetSpacerPerLevel
As for the second bug, you'd have to show the whole loop before one could say if it was a problem.

Portable way of finding total disk size in Java (pre java 6)

I need to find the total size of a drive in Java 5 (or 1.5, whatever). I know that Java 6 has a new method in java.io.File, but I need it to work in Java 5.
Apache Commons IO has org.apache.commons.io.FileSystemUtils to provide the free disk space, but not the total disk space.
I realize this is OS dependant and will need to depend on messy command line invocation. I'm fine with it working on "most" systems, i.e. windows/linux/macosx. Preferably I'd like to use an existing library rather than write my own variants.
Any thoughts? Thanks.
Update:
I apologise for misreading the question, I recommend copying the FileSystemUtils approach, but modifying the commands it runs slightly.
In dos you can get the free and total bytes with the fsutil command:
fsutil volume diskfree [drive letter]
on my box this gives the following results:
Total # of free bytes : 41707524096
Total # of bytes : 80023715840
Total # of avail free bytes : 41707524096
On Unix, the command is still "df -k", you're just interested in the "1024-blocks" column to the left of "Free" (example from Wikipedia below). You obviously need to multiply the result by 1024.
Filesystem 1024-blocks Free %Used Iused %Iused Mounted on
/dev/hd4 32768 16016 52% 2271 14% /
/dev/hd2 4587520 1889420 59% 37791 4% /usr
/dev/hd9var 65536 12032 82% 518 4% /var
/dev/hd3 819200 637832 23% 1829 1% /tmp
/dev/hd1 524288 395848 25% 421 1% /home
/proc - - - - - /proc
/dev/hd10opt 65536 26004 61% 654 4% /opt
Assuming you copy FileSystemUtils to implement "totalSpaceKB()" to delegate to an equivalent OS-specific method. The implementation for Windows would be something like this (note the use of "Find" to trim the output from fsutil to just get the total size):
long totalSpaceWindows(String path) throws IOException {
path = FilenameUtils.normalize(path);
if (path.length() > 2 && path.charAt(1) == ':') {
path = path.substring(0, 2); // seems to make it work
}
// build and run the 'fsutil' command
String[] cmdAttribs = new String[] {
"cmd.exe",
"/C",
"fsutil volume diskfree " + path
+ " | Find \"Total # of bytes\"" };
// read in the output of the command to an ArrayList
List lines = performCommand(cmdAttribs, Integer.MAX_VALUE);
//because we used Find, we expect the first line to be "Total # of bytes",
//you might want to add some checking to be sure
if (lines.size() > 0) {
String line = (String) lines.get(0);
String bytes = line.split(":")[1].trim();
return Long.parseLong(bytes);
}
// all lines are blank
throw new IOException(
"Command line 'fsutil volume diskfree' did not return
+ "any info for path '" + path + "'");
}
The implementation for Unix would be the same as freeSpaceUnix(), but remove the two calls to tok.nextToken() at the end of the method
/** comment these two lines so the token received is the total size */
tok.nextToken(); // Ignore 1K-blocks
tok.nextToken(); // Ignore Used
/** this will now be the total size */
String freeSpace = tok.nextToken();
return parseBytes(freeSpace, path);
}
The implementations for other platforms would be similar.
Hope this helps and apologies agian for misreading the problem.
Original answer (gets free bytes, not total).
Prior to Java 6 there isn't an elegant way to do this, (see the bug). Rather than rolling your own, I'd recommend using a library to do the platform-specific processing for you.
Apache commons-io has a FileSystemUtils type that provides a static method freeSpaceKb(). It works on Windows and and some Unix implementations (see quote from Javadoc below)
From the Javadoc:
public static long freeSpaceKb(String path)
throws IOException
Returns the free space on a drive or volume in kilobytes by invoking the command line.
FileSystemUtils.freeSpaceKb("C:"); // Windows
FileSystemUtils.freeSpaceKb("/volume"); // *nix
The free space is calculated via the command line. It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix.
In order to work, you must be running Windows, or have a implementation of Unix df that supports GNU format when passed -k (or -kP). If you are going to rely on this code, please check that it works on your OS by running some simple tests to compare the command line with the output from this class. If your operating system isn't supported, please raise a JIRA call detailing the exact result from df -k and as much other detail as possible, thanks.
Note: This solution is recommended only as a last ditch attempt
I do not know if this exists in any API/library. However, as a last ditch attempt I would probably create an Ant script or use Ant task implementations to execute an OS command appropriate to the platform and then parse the output stream. Why Ant? - because it provides:
Good platform identification (see os condition)
Platform independent command execution
Ability to parse output from command execution programmatically - by writing the output to a property and then accessing this from the Ant project object.
Yes, this is a little messy, but you could execute said target using the Ant API and then wrap it all up with your own interface. This still wouldn't necessarily cover all possible systems - only those that Ant can identify.
Additionally it is still a fair bit of work, you'll need to work out the correct commands and parsing strategies for all of your target platforms.
Again - last ditch attempt - so wait for other answers and continue your search for other libraries and API offerings.
Here at SUN there is a long history of the free disk space problem.
They mention JConfig as a possible solution. Unfortunately this library can only be reached at the end of 2009 summer (hopefully) and now it is not clear whether it solves your original problem since documentation and source is not available, but they state that:
It lets you work with files, web
browsers, processes, file types, and
other system-level items in a much
more advanced manner than that
provided by the standard Java class
libraries. For instance, you can use
it to launch web browsers or other
external applications instead of using
Runtime.exec or solutions that only
work on one platform.
So if you are patient and lucky ...

Compile time optimization of String concatenation

I'm curious, how far the optimization of the following code snippet will go.
To what I know, whenever the capacity of StringBuffer is extended, it costs some CPU work, because its content is required to be reallocated. However, I guess Java compiler optimization can precalculate the required capacity instead of doing multiple reallocations.
The question is: will the following snippet of code be optimized so?
public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
StringBuilder stringBuilder = new StringBuilder();
parameters.forEach(
(key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));
return baseURL + "?" + stringBuilder.delete(stringBuilder.length(),1);
}
In Java, most optimization is performed by the runtime's just in time compiler, so generally javac optimizations don't matter much.
As a consequence, a Java compiler is not required to optimize string concatenation, though all tend to do so as long as no loops are involved. You can check the extent of such compile time optimizations by using javap (the java decompiler included with the JDK).
So, could javac conceivably optimize this? To determine the length of the string builder, it would have to iterate the map twice. Since java does not feature const references, and the compiler has no special treatment for Map, the compiler can not determine that this rewrite would preserve the meaning of the code. And even if it could, it's not at all clear that the gains would be worth the cost of iterating twice. After all, modern processors can copy 4 to 8 characters in a single cpu instruction. Since memory access is sequential, there won't be any cache missing while growing the buffer. On the other hand, iterating the map a second time will likely cause additional cache misses, because the Map entries (and the strings they reference) can be scattered all over main memory.
In any case, I would not worry about the efficiency of this code. Even if your URL is 1000 characters long, resizing the buffer will take about 0.1 micro seconds. Unless you have evidence that this really is a performance hotspot, your time is probably better spent elsewhere.
First of all:
You can find out what (javac) compile time optimizations occur by looking at the bytecodes using the javap tool.
You can find out what JIT compiler optimizations are performed by getting the JVM to dump the native code.
So, if you need to know how your code has been optimized (on a particular platform) for practical reasons, then you should check.
In reality, the optimizations by javac are pretty simple-minded, and do not go to the extent of precalculating buffer sizes. I haven't checked, but I expect that the same is true for the JIT compiler. I doubt that it makes any attempt to preallocate a StringBuilder with an "optimal" size.
Why?
The reasons include the following:
An inaccurate precalculation (on average) doesn't help, and may be worse than doing nothing.
An accurate precalculation typically involves measuring the (dynamic) lengths of the actual strings to be joined.
Implementing the optimization logic would be complicated, and would make the optimizers slower and more effort to maintain.
At runtime, the String mensuration introduces overheads. Whether you would come out ahead often enough to make a difference is difficult to determine. (People don't like optimizations that make their code run slower ...)
There are better (more efficient) ways to do large scale text assembly than using String concatenation. The programmer (who has more knowledge of the problem domain and application logic) can optimize this better than a compiler. If it matters enough to spend the developer effort on this.
One optimization is to set the baseURL and ampersand in the stringBuilder instead of using the String concatenate at the end, such as:
public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
StringBuilder stringBuilder = new StringBuilder(baseURL);
stringBuilder.append("&");
parameters.forEach((key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));
stringBuilder.setLength(stringBuilder.length() - 1);
return stringBuilder.toString();
}
If you want a little more speed and since javac or JIT will not optimize based potential string size, you can track that yourself without incurring much overhead, but adding a max size tracker, such as this:
protected static URL_SIZE = 256;
public static String getGetRequestURL(String baseURL, Map<String, String> parameters) {
StringBuilder stringBuilder = new StringBuilder(URL_SIZE);
stringBuilder.append(baseURL);
stringBuilder.append("&");
parameters.forEach((key, value) -> stringBuilder.append(key).append("=").append(value).append("&"));
int size = stringBuilder.length();
if (size > URL_SIZE) {
URL_SIZE = size;
}
stringBuilder.setLength(size - 1);
return stringBuilder.toString();
}
That said, with some testing of 1 million calls, I found that the different version preformed as (in milliseconds):
Your version: total = 1151, average = 230
Above version 1: total = 936, average = 187
Above version 2: total = 839, average = 167

Categories