mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
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:
parent
d40aa622cf
commit
89a267ca46
6 changed files with 66 additions and 40 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue