Skip to content

Commit

Permalink
GH-3987 introduce InternedIRI for use in Vocabularies to improve perf…
Browse files Browse the repository at this point in the history
…romance
  • Loading branch information
hmottestad committed Jun 15, 2022
1 parent 0252fd6 commit a44b487
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ enum XSD implements CoreDatatype {
public static final String NAMESPACE = "http://www.w3.org/2001/XMLSchema#";

private static IRI iri(String localName) {
return new CoreDatatypeHelper.DatatypeIRI(NAMESPACE, localName);
return new InternedIRI(NAMESPACE, localName);
}

private final IRI iri;
Expand Down Expand Up @@ -275,7 +275,7 @@ enum RDF implements CoreDatatype {
public static final String NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";

private static IRI iri(String localName) {
return new CoreDatatypeHelper.DatatypeIRI(NAMESPACE, localName);
return new InternedIRI(NAMESPACE, localName);
}

private final IRI iri;
Expand Down Expand Up @@ -316,7 +316,7 @@ enum GEO implements CoreDatatype {
public static final String NAMESPACE = "http://www.opengis.net/ont/geosparql#";

private static IRI iri(String localName) {
return new CoreDatatypeHelper.DatatypeIRI(NAMESPACE, localName);
return new InternedIRI(NAMESPACE, localName);
}

private final IRI iri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,38 +43,4 @@ static Map<IRI, CoreDatatype> getReverseLookup() {
return reverseLookup;
}

static class DatatypeIRI extends AbstractIRI {

private static final long serialVersionUID = 169243624049169159L;

private final String namespace;
private final String localName;
private final String stringValue;

public DatatypeIRI(String namespace, String localName) {
this.namespace = namespace;
this.localName = localName;
this.stringValue = namespace.concat(localName).intern();
}

@Override
public String stringValue() {
return stringValue;
}

@Override
public String getNamespace() {
return namespace;
}

@Override
public String getLocalName() {
return localName;
}

@Override
public String toString() {
return stringValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*******************************************************************************
* Copyright (c) 2022 Eclipse RDF4J contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
******************************************************************************/

package org.eclipse.rdf4j.model.base;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Value;

/**
* An IRI implementation that interns the stringValue so that two objects can be compared by their stringValue
* reference. Must only be used for IRIs that are effectively ´public static final´ and only for a very limited number
* of objects because string interning affects the GC root set
* (https://shipilev.net/jvm/anatomy-quarks/10-string-intern/).
*
*/
@InternalUseOnly
public final class InternedIRI implements IRI {
private static final long serialVersionUID = 169243429049169159L;

private final String namespace;
private final String localName;
private final String stringValue;
private final int hashCode;

public InternedIRI(String namespace, String localName) {
this.namespace = namespace;
this.localName = localName;
this.stringValue = namespace.concat(localName).intern();
this.hashCode = stringValue.hashCode();
}

@Override
public String stringValue() {
return stringValue;
}

@Override
public String getNamespace() {
return namespace;
}

@Override
public String getLocalName() {
return localName;
}

@Override
public String toString() {
return stringValue;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (o instanceof InternedIRI) {
// Because the stringValue is interned we can simply compare the reference.
return stringValue == ((InternedIRI) o).stringValue;
}

if (o instanceof Value) {
Value value = (Value) o;
if (value.isIRI()) {
return stringValue.equals(value.stringValue());
}
}

return false;
}

@Override
public int hashCode() {
return hashCode;
}

private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(hashCode);
out.writeUTF(namespace);
out.writeUTF(localName);
}

private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
int hashCode = in.readInt();
String namespace = in.readUTF();
String localName = in.readUTF();

// Deserialization in Java typically uses Unsafe to set final fields, we need to use reflection.
writeToPrivateFinalField(namespace, "namespace");
writeToPrivateFinalField(localName, "localName");
writeToPrivateFinalField(hashCode, "hashCode");

// The main reason we need a custom deserialization is that we need to intern the stringValue field.
String stringValue = (namespace + localName).intern();
writeToPrivateFinalField(stringValue, "stringValue");
assert stringValue.hashCode() == hashCode;
}

private void writeToPrivateFinalField(String value, String fieldName) {
try {
Field field = InternedIRI.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(this, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

private void writeToPrivateFinalField(int value, String fieldName) {
try {
Field field = InternedIRI.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(this, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,57 @@

package org.eclipse.rdf4j.model.vocabulary;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.base.AbstractIRI;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.base.AbstractNamespace;
import org.eclipse.rdf4j.model.base.InternedIRI;

/**
* Utility methods related to RDF vocabularies.
*
* @author Alessandro Bollini
* @since 3.5.0
*
* @implNote To be eventually removed or merged with {@code org.eclipse.rdf4j.model.util.Vocabularies}.
* @since 3.5.0
*/
class Vocabularies {

static Namespace createNamespace(String prefix, String namespace) {
return new AbstractNamespace() {

private static final long serialVersionUID = 3913851292983866831L;

@Override
public String getPrefix() {
return prefix;
}

@Override
public String getName() {
return namespace;
}

};
private Vocabularies() {
}

static IRI createIRI(String namespace, String localName) {
return new AbstractIRI() {

private static final long serialVersionUID = 1692436252019169159L;
static Namespace createNamespace(String prefix, String namespace) {
return new VocabularyNamespace(prefix, namespace);
}

// ;( removing .toString() causes a 2x penalty in .equals() performance on Oracle JDK 1.8/11…
private static class VocabularyNamespace extends AbstractNamespace {

private final String stringValue = (namespace + localName).toString();
private final String prefix;
private final String namespace;

@Override
public String stringValue() {
return stringValue;
}
public VocabularyNamespace(String prefix, String namespace) {

@Override
public String getNamespace() {
return namespace;
}
this.prefix = prefix;
this.namespace = namespace;
}

@Override
public String getLocalName() {
return localName;
}
@Override
public String getPrefix() {
return prefix;
}

};
@Override
public String getName() {
return namespace;
}
}

private Vocabularies() {
static IRI createIRI(String namespace, String localName) {
return new InternedIRI(namespace, localName);
}

}

0 comments on commit a44b487

Please sign in to comment.