3

Short prestory. Typical problem: you have a lot of small string and you want to concatenate them all in one big string. The code like

for(Integer i = 0; i < smallStringsSize; i++){
    bigString += smallStrings[i];
}

will solve this problem, but it has to produce a lot of unnecessary string objects that consume your heap. That is why Java has classes like StringBuilder and StringBuffer that allow you to solve this problem wihtout consuming too many memory. Thus, I decided to try to do my own StringBuilder in apex. String class has two methods that looked suitable for it: String.join and String.fromCharArray . Here is the code that i used to test these methods against "+=" concatenantion (ready to execute in anonymous):

List<String> chars = new List<String>();
List<Integer> charsCode = new List<Integer>();
Integer size = 10000;
String chartToAdd = 'Ω';
for(Integer i = 0; i < size; i++){
    chars.add(chartToAdd);
    charsCode.add(String.valueOf(chartToAdd).charAt(0));
}
String joinS = '', forS = '', fromS = '';
Integer initialHeapSize = Limits.getHeapSize();
Integer initialCPUtime = Limits.getCpuTime();
System.debug('initialHeapSize ' + initialHeapSize);
System.debug('initialCPUtime ' + initialCPUtime);
joinS = String.join(chars, '');
Integer joinHeapSize = Limits.getHeapSize() - initialHeapSize;
Integer joinCPUtime = Limits.getCpuTime() - initialCPUtime;
System.debug('-------- String.join ---------');
System.debug('joinHeapSize ' + joinHeapSize);
System.debug('joinCPUtime ' + joinCPUtime);
for(Integer i = 0; i < size; i++){
    forS += chars[i];
}
Integer forHeapSize = Limits.getHeapSize() - joinHeapSize - initialHeapSize;
Integer forCPUtime = Limits.getCpuTime() - joinCPUtime - initialCPUtime;
System.debug('-------- for each += ---------');
System.debug('forHeapSize ' + forHeapSize);
System.debug('forCPUtime ' + forCPUtime);
fromS = String.fromCharArray(charsCode);
Integer fromHeapSize = Limits.getHeapSize() - forHeapSize - joinHeapSize - initialHeapSize;
Integer fromCPUtime = Limits.getCpuTime() - forCPUtime - joinCPUtime - initialCPUtime;
System.debug('-------- String.fromCharArray ---------');
System.debug('fromHeapSize ' + fromHeapSize);
System.debug('fromCPUtime ' + fromCPUtime);
System.debug('');
System.debug('-------- strings ----------');
System.debug('joins ' + joins);
System.debug('fors ' + fors);
System.debug('fromS ' + fromS);

The result of this code execution:

initialHeapSize 161166

initialCPUtime 242

-------- String.join ---------

joinHeapSize 10016

joinCPUtime 17

-------- for each += ---------

forHeapSize 10024

forCPUtime 48

-------- String.fromCharArray ---------

fromHeapSize 10016

fromCPUtime 5

As you can see, the heap size for each approach is so close to 10 kilobytes, what is a size of the string of 10 000 characters (size) if each character takes only one byte. In case of creating 10 000 additional string objects, the heap size would be a bit more than 50 megabytes (10 000 * (1 + 10 000)/2). Taking into consideration the above,

  1. Does not the '+=' concatenation create any additional string objects that devour your heap? Did not I miss anything? I am so amazed.

  2. As you see, I use Omega 'Ω' as a chartToAdd. It has the high-surrogate code point equals to 937, that needs at least 2 bytes to be encoded. How can 10 000 Omegas consume only 10 000 bytes of the heap size?

  3. What weightful reasons Salesforce has that they do not to add the character data type? Any ideas?

  • 2
    Note that you can use salesforce method: join, for concatenating list elements into single string. – Liron C Nov 2 '16 at 15:33
3

Does not the '+=' concatenation create any additional string objects that devour your heap? Did not I miss anything? I am so amazed.

The garbage collector (GC) is particularly aggressive, presumably so you don't blow governor limits when you're not actually over the heap limit size. The debug logs don't show deallocations, but presumably Limits.getHeapSize happens to know exactly which objects in memory have at least one reference and therefore can't be GC'd. If I had to guess, I'd say that salesforce probably uses a ref-count GC model, where each object has a number associated with it that increments by one for each reference to it, and decrements by one when the reference is removed. This is only conjecture, as they don't tell us how it works, but it doesn't really matter; this is the effect that we observe.

As you see, I use Omega 'Ω' as a chartToAdd. It has the high-surrogate code point equals to 937, that needs at least 2 bytes to be encoded. How can 10 000 Omegas consume only 10 000 bytes of the heap size?

For some reason that's also not documented, a string's "byte count" appears to be the number of 16-bit code points needed. For example, this code does yield a byte size of 20,000 bytes:

System.debug(Limits.getHeapSize());
String a = '𓄪'.repeat(10000);
System.debug(Limits.getHeapSize());

That character is Unicode 0x1312a. The cutoff for counting as one byte happens to be 0xFFFF; starting with 0x10000, characters start counting as two "bytes" per character. I'm not sure why this behavior exists, as it doesn't appear in any documentation, but basically you just need to be aware that all "normal" language characters should count as one byte each. Emojis like 😁 (0x1F601) will count as two bytes each.

What weightful reasons Salesforce has that they do not to add the character data type? Any ideas?

We're not told, but I presume it's for performance and/or security reasons. We also don't have the Byte data type, which means we can't examine binary data easily. I guess they don't want us to peek under the hood too much and figure out the internal details of the platform. For example, is it big-endian, little-endian, or some other special bit arrangement?

Without the Character and Byte classes, we're abstracted away from the hardware in ways that encourage us to stay within typical business use cases, and not overload the servers with things like ZIP decompression, image scaling, and other intensive activities that would be better hosted on other platforms or even on the client.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.