8221836: Avoid recalculating String.hash when zero

Co-authored-by: Peter Levart <peter.levart@gmail.com>
Reviewed-by: jrose, adinn
This commit is contained in:
Claes Redestad 2019-04-10 12:05:50 +02:00
parent d40aa622cf
commit 89a267ca46
6 changed files with 66 additions and 40 deletions

View file

@ -160,6 +160,7 @@ static void compute_offset(int& dest_offset, InstanceKlass* ik,
int java_lang_String::value_offset = 0; int java_lang_String::value_offset = 0;
int java_lang_String::hash_offset = 0; int java_lang_String::hash_offset = 0;
int java_lang_String::hashIsZero_offset = 0;
int java_lang_String::coder_offset = 0; int java_lang_String::coder_offset = 0;
bool java_lang_String::initialized = false; bool java_lang_String::initialized = false;
@ -179,7 +180,8 @@ bool java_lang_String::is_instance(oop obj) {
#define STRING_FIELDS_DO(macro) \ #define STRING_FIELDS_DO(macro) \
macro(value_offset, k, vmSymbols::value_name(), byte_array_signature, false); \ macro(value_offset, k, vmSymbols::value_name(), byte_array_signature, false); \
macro(hash_offset, k, "hash", int_signature, false); \ macro(hash_offset, k, "hash", int_signature, false); \
macro(coder_offset, k, "coder", byte_signature, false) macro(hashIsZero_offset, k, "hashIsZero", bool_signature, false); \
macro(coder_offset, k, "coder", byte_signature, false);
void java_lang_String::compute_offsets() { void java_lang_String::compute_offsets() {
if (initialized) { if (initialized) {
@ -507,18 +509,38 @@ jchar* java_lang_String::as_unicode_string(oop java_string, int& length, TRAPS)
} }
unsigned int java_lang_String::hash_code(oop java_string) { unsigned int java_lang_String::hash_code(oop java_string) {
typeArrayOop value = java_lang_String::value(java_string); // The hash and hashIsZero fields are subject to a benign data race,
int length = java_lang_String::length(java_string, value); // making it crucial to ensure that any observable result of the
// Zero length string will hash to zero with String.hashCode() function. // calculation in this method stays correct under any possible read of
if (length == 0) return 0; // these fields. Necessary restrictions to allow this to be correct
// without explicit memory fences or similar concurrency primitives is
bool is_latin1 = java_lang_String::is_latin1(java_string); // that we can ever only write to one of these two fields for a given
// String instance, and that the computation is idempotent and derived
if (is_latin1) { // from immutable state
return java_lang_String::hash_code(value->byte_at_addr(0), length); assert(initialized && (hash_offset > 0) && (hashIsZero_offset > 0), "Must be initialized");
} else { if (java_lang_String::hash_is_set(java_string)) {
return java_lang_String::hash_code(value->char_at_addr(0), length); return java_string->int_field(hash_offset);
} }
typeArrayOop value = java_lang_String::value(java_string);
int length = java_lang_String::length(java_string, value);
bool is_latin1 = java_lang_String::is_latin1(java_string);
unsigned int hash = 0;
if (length > 0) {
if (is_latin1) {
hash = java_lang_String::hash_code(value->byte_at_addr(0), length);
} else {
hash = java_lang_String::hash_code(value->char_at_addr(0), length);
}
}
if (hash != 0) {
java_string->int_field_put(hash_offset, hash);
} else {
java_string->bool_field_put(hashIsZero_offset, true);
}
return hash;
} }
char* java_lang_String::as_quoted_ascii(oop java_string) { char* java_lang_String::as_quoted_ascii(oop java_string) {

View file

@ -94,6 +94,7 @@ class java_lang_String : AllStatic {
private: private:
static int value_offset; static int value_offset;
static int hash_offset; static int hash_offset;
static int hashIsZero_offset;
static int coder_offset; static int coder_offset;
static bool initialized; static bool initialized;
@ -132,6 +133,10 @@ class java_lang_String : AllStatic {
assert(initialized && (hash_offset > 0), "Must be initialized"); assert(initialized && (hash_offset > 0), "Must be initialized");
return hash_offset; return hash_offset;
} }
static int hashIsZero_offset_in_bytes() {
assert(initialized && (hashIsZero_offset > 0), "Must be initialized");
return hashIsZero_offset;
}
static int coder_offset_in_bytes() { static int coder_offset_in_bytes() {
assert(initialized && (coder_offset > 0), "Must be initialized"); assert(initialized && (coder_offset > 0), "Must be initialized");
return coder_offset; return coder_offset;
@ -139,12 +144,11 @@ class java_lang_String : AllStatic {
static inline void set_value_raw(oop string, typeArrayOop buffer); static inline void set_value_raw(oop string, typeArrayOop buffer);
static inline void set_value(oop string, typeArrayOop buffer); static inline void set_value(oop string, typeArrayOop buffer);
static inline void set_hash(oop string, unsigned int hash);
// Accessors // Accessors
static inline typeArrayOop value(oop java_string); static inline typeArrayOop value(oop java_string);
static inline typeArrayOop value_no_keepalive(oop java_string); static inline typeArrayOop value_no_keepalive(oop java_string);
static inline unsigned int hash(oop java_string); static inline bool hash_is_set(oop string);
static inline bool is_latin1(oop java_string); static inline bool is_latin1(oop java_string);
static inline int length(oop java_string); static inline int length(oop java_string);
static inline int length(oop java_string, typeArrayOop string_value); static inline int length(oop java_string, typeArrayOop string_value);

View file

@ -45,9 +45,9 @@ void java_lang_String::set_value(oop string, typeArrayOop buffer) {
string->obj_field_put(value_offset, (oop)buffer); string->obj_field_put(value_offset, (oop)buffer);
} }
void java_lang_String::set_hash(oop string, unsigned int hash) { bool java_lang_String::hash_is_set(oop java_string) {
assert(initialized && (hash_offset > 0), "Must be initialized"); assert(initialized && (hash_offset > 0) && (hashIsZero_offset > 0), "Must be initialized");
string->int_field_put(hash_offset, hash); return java_string->int_field(hash_offset) != 0 || java_string->bool_field(hashIsZero_offset) != 0;
} }
// Accessors // Accessors
@ -71,12 +71,6 @@ typeArrayOop java_lang_String::value_no_keepalive(oop java_string) {
return (typeArrayOop) java_string->obj_field_access<AS_NO_KEEPALIVE>(value_offset); return (typeArrayOop) java_string->obj_field_access<AS_NO_KEEPALIVE>(value_offset);
} }
unsigned int java_lang_String::hash(oop java_string) {
assert(initialized && (hash_offset > 0), "Must be initialized");
assert(is_instance(java_string), "must be java_string");
return java_string->int_field(hash_offset);
}
bool java_lang_String::is_latin1(oop java_string) { bool java_lang_String::is_latin1(oop java_string) {
assert(initialized && (coder_offset > 0), "Must be initialized"); assert(initialized && (coder_offset > 0), "Must be initialized");
assert(is_instance(java_string), "must be java_string"); assert(is_instance(java_string), "must be java_string");

View file

@ -761,8 +761,6 @@ struct CopyToArchive : StackObj {
return true; return true;
} }
unsigned int hash = java_lang_String::hash_code(s); unsigned int hash = java_lang_String::hash_code(s);
java_lang_String::set_hash(s, hash);
oop new_s = StringTable::create_archived_string(s, Thread::current()); oop new_s = StringTable::create_archived_string(s, Thread::current());
if (new_s == NULL) { if (new_s == NULL) {
return true; return true;

View file

@ -351,19 +351,14 @@ void StringDedupTable::deduplicate(oop java_string, StringDedupStat* stat) {
unsigned int hash = 0; unsigned int hash = 0;
if (use_java_hash()) { if (use_java_hash()) {
// Get hash code from cache if (!java_lang_String::hash_is_set(java_string)) {
hash = java_lang_String::hash(java_string); stat->inc_hashed();
} }
hash = java_lang_String::hash_code(java_string);
if (hash == 0) { } else {
// Compute hash // Compute hash
hash = hash_code(value, latin1); hash = hash_code(value, latin1);
stat->inc_hashed(); stat->inc_hashed();
if (use_java_hash() && hash != 0) {
// Store hash code in cache
java_lang_String::set_hash(java_string, hash);
}
} }
typeArrayOop existing_value = lookup_or_add(value, latin1, hash); typeArrayOop existing_value = lookup_or_add(value, latin1, hash);

View file

@ -164,6 +164,12 @@ public final class String
/** Cache the hash code for the string */ /** Cache the hash code for the string */
private int hash; // Default to 0 private int hash; // Default to 0
/**
* Cache if the hash has been calculated as actually being zero, enabling
* us to avoid recalculating this.
*/
private boolean hashIsZero; // Default to false;
/** use serialVersionUID from JDK 1.0.2 for interoperability */ /** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L; private static final long serialVersionUID = -6849794470754667710L;
@ -1508,14 +1514,21 @@ public final class String
* @return a hash code value for this object. * @return a hash code value for this object.
*/ */
public int hashCode() { public int hashCode() {
// The hash or hashIsZero fields are subject to a benign data race,
// making it crucial to ensure that any observable result of the
// calculation in this method stays correct under any possible read of
// these fields. Necessary restrictions to allow this to be correct
// without explicit memory fences or similar concurrency primitives is
// that we can ever only write to one of these two fields for a given
// String instance, and that the computation is idempotent and derived
// from immutable state
int h = hash; int h = hash;
if (h == 0 && value.length > 0) { if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value) h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value); : StringUTF16.hashCode(value);
// Avoid issuing a store if the calculated value is also zero: if (h == 0) {
// in addition to a minor performance benefit, this allows storing hashIsZero = true;
// Strings with zero hash code in read-only memory. } else {
if (h != 0) {
hash = h; hash = h;
} }
} }