Project

General

Profile

Bug #9060

Cache oftenly used values that are immutable

Added by Alexandru Lungu almost 2 years ago. Updated 4 months ago.

Status:
Internal Test
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

90%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
production:
No
env_name:
topics:

search_results.txt Magnifier (6.78 KB) Ioana-Cristina Prioteasa, 02/11/2025 08:37 AM

CheckAssignments.java Magnifier (3.2 KB) Ioana-Cristina Prioteasa, 02/17/2025 05:13 AM


Related issues

Related to Database - Bug #9872: Runtime import statement relies on the ImportWorker to read lob data. New
Related to Base Language - Bug #10004: NativeInvoker fails if an argument is of type integerConstant WIP

History

#1 Updated by Alexandru Lungu almost 2 years ago

  • Subject changed from Cache oftenly used literals that are immutable to Cache oftenly used values that are immutable
  • Assignee set to Ioana-Cristina Prioteasa

As part of the profiling work, the GC activity was detected as being high. This may be caused by the high number of allocations / deallocations of objects.
To reduce this, we can identify the immutable converted values and cache them. This was done already for logical, implementing logical.TRUE, logical.FALSE and logical.UNKNOWN (that can be retrieved using logical.of).

The point is to extend this technique to other types like: integer (cache first N integers in a static array), characters (cache EMPTY character and one-letter ASCII characters) and maybe others.

However, note that only the immutable occurrences can be cached: the result of MathOps, DynamicOps, etc. Variables themselves or other writable values can't be cached. Thus, we should reuse the principle from logical.logicalConstant to enforce immutability.

We don't have an exact measure of such improvement, so we should start with that: count how many such instances are created from immutable contexts.

#2 Updated by Ioana-Cristina Prioteasa over 1 year ago

  • Status changed from New to WIP

In integer.java, I implemented a mechanism to count the number of instances created and saved the results to a CSV file. This was performed during server startup and one performance test to analyze the instance creations and identify the most frequently used integers.

The top 25 results are as follows:
Value Count
Unknown 174014
0 410683
5 187010
1 128977
4 55694
2 38967
3 34061
20 29244
80 23673
170 23077
89 20577
10 20460
38 17940
8 15333
30 13681
236 12496
40 12481
275 12449
63 11136
21 10779
-300 9793
7 9124
6 8530
9 8481
60 8166
50 7584

Notably, all integers from 0 to 10 appear in the top 25 most used values, suggesting that caching these integers would be an effective starting point for this optimization.

I also attempted to count the integers returned by MathOps and DynamicOps using the same test scenario, but this yielded no results. I am pretty unsure how I could determine what integers are coming from an immutable context.

#3 Updated by Eric Faulhaber over 1 year ago

  • reviewer Constantin Asofiei added

#4 Updated by Constantin Asofiei over 1 year ago

Please check every usage from p2j.oo, of MathOps and DynamicOps, so there is nothing like, for example, integer i1 = MathOps.plus instead of i1.assign(MathOps.plus).

#5 Updated by Ioana-Cristina Prioteasa over 1 year ago

  • reviewer deleted (Constantin Asofiei)
Committed 9060a, rev. 15659-15661:
  • Added integerConstant and cached the first 10 integers.
  • Added int64Constant and cached the first 10 int64 values.
  • Provided support for the above.
  • Utilized integer.of, int64.of, logical.of and character.of in immutable contexts for performance improvement.

I logged the source of the most frequently used integers for integer.of, and applied caching where possible. In a performance test on a big customer application, this resulted in the use of 580k cached integers at server startup and an additional 42k cached integers during the test.

My plan is to apply this method to identify the most commonly used int64, logical and character constants and cache them, increasing our performance gains. For characters, I'll need to be selective to avoid overloading the cache, focusing on scenarios with limited possibilities.

Currently, the integer cache size is set to 10. I'll measure performance across different sizes to determine the optimal value. Additionally, this setting could be made configurable in directory.xml.

I think the changes so far are ready for review to ensure I'm on the right track.
Constantin, please review 9060a, revisions 15659-15661.

#6 Updated by Ioana-Cristina Prioteasa over 1 year ago

  • version_resolved set to cons
  • reviewer Constantin Asofiei added

Constantin Asofiei wrote:

Please check every usage from p2j.oo, of MathOps and DynamicOps, so there is nothing like, for example, integer i1 = MathOps.plus instead of i1.assign(MathOps.plus).

I will assess this next, thank you.

#7 Updated by Ioana-Cristina Prioteasa over 1 year ago

  • version_resolved deleted (cons)

#8 Updated by Ioana-Cristina Prioteasa over 1 year ago

I found multiple occurences of = MathOps. in our code.
In TotalAccumulator.accumulate:

      public void accumulate(BaseDataType datum)
      {
         total = MathOps.plus(total, (NumberType) datum);
      }

Similar in AverageAccumulator.
In Operators there are a few uses similar to:
         int64 mod = MathOps.modulo(new int64(a), new decimal(b));
         return mod.isUnknown() ? null : mod.longValue();

In ExpressionEvaluator.evaluate there are some cases similar to this:
if (op1 instanceof NumberType)
{
   r = MathOps.plus((NumberType) op1, (NumberType) op2);
}
//more code
return r;

In ToClause.incrementAndTest:
         @// get the current loop control value
         val = var.get();

         // calculate the new loop control value
         if (num)
         {
            NumberType number = (NumberType) val;
            val = MathOps.plus(number, factor); 
         }
         else if (val instanceof datetime) // datetime-tz also
         {
            datetime dd = (datetime) val;
            val = datetime.plusMillis(dd, factor);
         }
         else if (val instanceof date)
         {
            date dd = (date) val;
            val = date.plusDays(dd, factor);
         }
         // else some incorrect looping datatype, probably should throw an exception

         // update the loop control var
         var.set(val);

In total I found the above construct about 15 times, but = TextOps. appears closer to 100 times and = CompareOps. again about 15 times. I can go thru and change these to an assign construct, but for example here I don t think it is necessary as the integer is not used int the next steps.

int64 mod = MathOps.modulo(new int64(a), new decimal(b));
return mod.isUnknown() ? null : mod.longValue();

Should I only change to assign in the places where the value is returned or used in the next lines?

#9 Updated by Constantin Asofiei over 1 year ago

As long as the Java variable assigned to that reference of the i.e. integerConstant is using it in a read-only way, is OK to leave it as-is. But it must not be returned to the application runtime, it should stay in the FWD runtime.

#10 Updated by Ioana-Cristina Prioteasa over 1 year ago

Seems pretty difficult to determine that for sure in all use cases. Using assign is the better way to go right?
It may be that I will spend more time determining if the value is used only in read only than just using assign.

#11 Updated by Constantin Asofiei over 1 year ago

Focus on what is obvious. We can always come back to something which stands up in profiling.

#12 Updated by Ioana-Cristina Prioteasa over 1 year ago

Committed on 9060a rev 15662, 15663 multiple uses of logical and int64 constants.
Tested on a large customer application, where during server startup, approximately 250k logical and 25k int64 instances are retrieved from cache. During a performance test run, an additional 1,000k logical and 250k int64 instances are reused.

#13 Updated by Ioana-Cristina Prioteasa over 1 year ago

Committed on 9060a rev 15664 multiple uses of character constants. These are used where the value has limited options.
Tested on a large customer application and during server startup we reuse 160k character.UNKNOWN, 60k character.EMPTY_STRING and 90k values retrieved from the cache. At this step the cache contains 17 values.
Then, during a performance test run we reuse an extra 5k character.UNKNOWN and 200k values retrieved from the cache. After this step the cache contains 31 values.

I am unsure if it is ok to use character.of in LegacyLogManager.getLogFileName() - this contains customer information but in theory is has a limited pool of values and it is used a lot of times, almost 60k times.

#14 Updated by Constantin Asofiei over 1 year ago

Ioana-Cristina Prioteasa wrote:

I am unsure if it is ok to use character.of in LegacyLogManager.getLogFileName() - this contains customer information but in theory is has a limited pool of values and it is used a lot of times, almost 60k times.

Go ahead and cache these. I think we should add a hard-limit to the character.of() cache to i.e. 2048 or something like this, for runtime.

#15 Updated by Ioana-Cristina Prioteasa over 1 year ago

Constantin Asofiei wrote:

Go ahead and cache these. I think we should add a hard-limit to the character.of() cache to i.e. 2048 or something like this, for runtime.

Got it, i will implement that next.

#16 Updated by Ioana-Cristina Prioteasa over 1 year ago

Committed to 9060a rev. 15665: limited character cache size, added directory.xml options to determine the character, integer and int64 cache sizes and some quick fixes.
What remains to be done here is checking where construct like integer i1 = MathOps.plus appear. I will move to that.

#17 Updated by Alexandru Lungu over 1 year ago

Following the meeting today on how H2 is doing the caching for int values:
  • static cache of configurable size.
    • it is an array where Nth position stores the int value for N.
    • this can be lazily initiated and the collection size is bound.
  • dynamic cache of configurable size (lets say N).
    • it is an array with a bound size.
    • if an integer X is not in the static cache, then look into the dynamic cache. For this, compute X % N to gain a quick hash of X < N. PS: maybe use the absolute value to also cover negative values.
    • if there is something at the X % N position, then check if what is found is the right instance (e.g. dynamic_cache[x % n].value() == x % n).
      • if this holds, there you go ... cache hit
      • if not, then create a new instance and override the one from dynamic_cache[x % n].
    • if there is nothing at the X % N position, then ... cache miss. create a new instance and store it in dynamic_cache[x % n]

The gain of the dynamic cache: we still cache recently used large integers.
The downside of the dynamic cache: there is a low chance (depending on the collection size and collision probability) to trash the dynamic_cache on a very specific position.

#18 Updated by Ioana-Cristina Prioteasa over 1 year ago

Ioana-Cristina Prioteasa wrote:

What remains to be done here is checking where construct like integer i1 = MathOps.plus appear. I will move to that.

I created a script that scanned our codebase for usages of constructs like = MathOps., for MathOps, CompareOps, DynamicOps, EnvironmentOps and TextOps. I have attached the output of that search. I manually reviewed all occurrences.

Initially, I committed a change related to this in rev. 15666, but later uncommitted it in rev. 15668, as I realized the = usage was incorrect in that case. After reviewing all instances, I confirmed that none require an explicit call to assign.

In revision 15667, I updated CacheManager to support the creation of an array cache and an IdentityHashMap cache. I also made CacheManager.getCacheSize public to allow retrieving the maximum configured size for the character cache.

My next step is to test a large customer GUI application and also evaluate performance to assess the impact of 9060. Once we have a clearer understanding, we can consider implementing the dynamic cache approach suggested by Alex in #9060-17.

#19 Updated by Ioana-Cristina Prioteasa over 1 year ago

Tested a big GUI customer application and a NPE showed up, caused by the use of CacheManager.
character.of was called before CacheManger.initialize, causing the cache in character to be null.
I think CacheManager was initially designed to initialize the persistence caches, as it's initialize method is called in Persistence.initialize. Since then, it's scope seemed to extend beyond persistence. I think it's a good idea for it to be refactored, add a server hook to initialize the CacheManager and modify it's configuration path from directory.xml from server/default/persistence to server/default.
Does this sound like a good approach?

After a quick fix for the NPE, the tested application showed no regressions, this is good news!

#20 Updated by Eric Faulhaber over 1 year ago

Ioana-Cristina Prioteasa wrote:

...and modify it's configuration path from directory.xml from server/default/persistence to server/default.
Does this sound like a good approach?

It makes sense, since the CacheManager is no longer used only for persistence. However, there are applications in production and testing which this change will break (well, cache configuration will be lost, which can have unexpected performance and memory consequences, as defaults are used).

To avoid using the hard-coded cache defaults, I think we need to prefer these new directory paths, but honor the old ones if they're found (and not overridden), logging a warning to alert admins that these settings are deprecated. Any new caches (like the one for this task) will only be read from the new location.

The cache configuration documentation will need updates as well (including a note about the deprecation of the old location).

#21 Updated by Ioana-Cristina Prioteasa over 1 year ago

Eric Faulhaber wrote:

To avoid using the hard-coded cache defaults, I think we need to prefer these new directory paths, but honor the old ones if they're found (and not overridden), logging a warning to alert admins that these settings are deprecated. Any new caches (like the one for this task) will only be read from the new location.

The initialization of cache sizes is handled in CacheManager, which searches for all containers inside /server/default/persistent/cache-sizes/ in the directory and builds a map of cache sizes based on all configurations found. When an actual cache is initialized in a specific class, it looks up its configured size in this map — if not found, it falls back to a default value.

The challenge here is that CacheManager does not inherently differentiate between old and newly added caches. Given this, the best approach seems to be:

  1. First, initialize from /server/default/persistence. - Log a warning for each cache size found in this deprecated location.
  2. Then, read from /server/default. - Any values found here will override those from the deprecated location.

At the end of this process, we will have cache sizes for all defined caches while also providing clear deprecation warnings for any values retrieved from /server/default/persistence.

#22 Updated by Alexandru Lungu over 1 year ago

The challenge here is that CacheManager does not inherently differentiate between old and newly added caches. Given this, the best approach seems to be:

I am not quite sure what you mean by old and new. Each time you demand a new cache from the CacheManager it will oblige, so I would say that these are always "new caches".

#23 Updated by Eric Faulhaber over 1 year ago

Alexandru Lungu wrote:

I am not quite sure what you mean by old and new.

"Old" (deprecated) path: /server/default/persistence/cache-sizes/
"New" path: /server/default/cache-sizes/

#24 Updated by Alexandru Lungu over 1 year ago

Got it - good approach in #9060-21 then.

#25 Updated by Dănuț Filimon over 1 year ago

I also agree with #9060-21, but make sure you update the wiki to reflect this change https://proj.goldencode.com/projects/p2j/wiki/Database_Configuration#Cache-Sizes, including the warnings when using the deprecated path.

#26 Updated by Constantin Asofiei over 1 year ago

Dănuț Filimon wrote:

I also agree with #9060-21, but make sure you update the wiki to reflect this change https://proj.goldencode.com/projects/p2j/wiki/Database_Configuration#Cache-Sizes, including the warnings when using the deprecated path.

We also need to migrate these paths in existing applications - maybe we can do this auto-magically, at FWD server startup?

#27 Updated by Ioana-Cristina Prioteasa over 1 year ago

Tested performance with 9060a changes using the default cache sizes for:
  • integer - 1024 (integer with values 0-1021 cached)
  • int64 - 1024 (int64 with values 0-1021 cached)
  • character - 2048 (the character cache cannot exceed 2048)

The overall execution time decreased by 2%, but certain steps in the performance test showed improvements of up to 13%. This is a significant result and the impact of these changes is clearly visible.

I will work next on the CacheManager and the dynamic cache Alex explained in #9060-17, maybe we can increase the impact of this even more.

#28 Updated by Ioana-Cristina Prioteasa over 1 year ago

  • % Done changed from 0 to 90
  • Status changed from WIP to Review
Committed on 9060a:
  • rev. 15669: Refactored CacheManager.
  • rev. 15670: Added dynamic cache to integer and int64.

I am working right now on a post to help tracking the changes, but as for now 9060a is ready for review.
Moved to 90%, the default cache sizes can be fine tuned.

#29 Updated by Ioana-Cristina Prioteasa over 1 year ago

I will go thru the modified classes and what changes are included in each, to help with the review process.
Main implementation and changes:
  • com.goldencode.p2j.util.int64 - Added int64Constant, static and dynamic cache using the CacheManager.
  • com.goldencode.p2j.util.integer - Added integerConstant, static and dynamic cache using CacheManager
  • com.goldencode.p2j.util.character - Used the CacheManager to instantiate the cache and added a hard limit to the size of the cache.
  • com.goldencode.p2j.persist.dialect.P2JSQLServer2008Dialect - Added support for integer and int64 constants.
  • com.goldencode.p2j.persist.hql.DataTypeHelper - Added support for integer and int64 constants.
  • com.goldencode.p2j.persist.orm.types.TypeManager - Added support for integer and int64 constants.
  • com.goldencode.p2j.util.BaseDataTypeFactory - Added support for integer and int64 constants.
  • com.goldencode.p2j.util.DataSetSDOHelper - Added support for integer and int64 constants.
  • com.goldencode.p2j.util.jobject - Added support for integer and int64 constants.
  • com.goldencode.p2j.persist.CacheManager - added APIs for an array cache and an identity hash map cache and refactored to read the configuration first from /persistence/cache-size, register the sizes found there, but log a deprecated location warning and then check the new /cache-size location, add everything new found there and override the deprecated values if necessary.
  • com.goldencode.p2j.main.StandardServer - added a server hook to initialize the CacheManager.
  • com.goldencode.p2j.persist.Persistence - removed the initialization of the CacheManager.
Used logicalConstant:
  • com.goldencode.p2j.oo.lang.LegacyClass in isA method.
  • com.goldencode.p2j.persist.BufferFieldImpl in literalQuestion method.
  • com.goldencode.p2j.persist.ConnectionManager in connectedImpl method.
  • com.goldencode.p2j.persist.DataSource in addSourceBufferImpl method.
  • com.goldencode.p2j.persist.DynamicConversionHelper in injectVariable method.
  • com.goldencode.p2j.persist.DynamicTablesHelper in generateSchemaAst method.
  • com.goldencode.p2j.persist.P2JQuery in getNext method.
  • com.goldencode.p2j.persist.Presorter in isFirstOfGroup method.
  • com.goldencode.p2j.persist.RecordBuffer in delete method.
  • com.goldencode.p2j.persist.StaticTempTable in canUndo, clear and addFieldToIndex methods.
  • com.goldencode.p2j.persist.TableMapper in isLegacyFieldMandatory and loadFields methods.
  • com.goldencode.p2j.persist.TempTableBuilder in addNewField, @addNewIndex, addFieldToIndex, tempTablePrepareImpl createTableLikeImpl, addIndexLike and parseIndexString methods.
  • com.goldencode.p2j.persist.meta.MetadataManager in populateTenant, childData, indexData, fieldData and build methods.
  • com.goldencode.p2j.util.ArrayAssigner in assignMulti and assignSingle methods.
  • com.goldencode.p2j.util.ErrorManager in isError method.
  • com.goldencode.p2j.util.LegacyLogManagerImpl in writeMessage method.
  • com.goldencode.p2j.util.NumberType in setNumericFormat method.
  • com.goldencode.p2j.util.ProcedureManager in addSuperProcedureImpl and isPersistent methods.
  • com.goldencode.p2j.util.SessionUtils in isErrorStackTrace method.
  • com.goldencode.p2j.util.XDocumentImpl in load method.
  • com.goldencode.p2j.util.XEntityImpl in getChild, removeChild and removeAttribute methods.
  • com.goldencode.p2j.util.XNodeRefImpl in nodeValueToMemptr and normalize methods.
Used characterConstant:
  • com.goldencode.p2j.persist.AbstractTempTable in namespacePrefix method.
  • com.goldencode.p2j.persist.RecordBuffer in delete method.
  • com.goldencode.p2j.persist.meta.MetadataManager in addTableToFile, fieldData and build methods.
  • com.goldencode.p2j.util.EnvironmentOps in getCurrentLanguage, getOperatingSystem and getRuntimeOperatingSystem methods.
  • com.goldencode.p2j.util.HandleChain in name method.
  • com.goldencode.p2j.util.HandleResource in resourceType method.
  • com.goldencode.p2j.util.LegacyLogManagerImpl in getLogFileName method.
  • com.goldencode.p2j.util.NumberType in getNumericFormat and setNumericFormat methods.
  • com.goldencode.p2j.util.date in getDateOrder method.
Used integerConstant:
  • com.goldencode.p2j.persist.BufferFieldImpl in extent method.
  • com.goldencode.p2j.persist.BufferImpl in numFields method.
  • com.goldencode.p2j.persist.DataSet in numBuffers and bufferHandle methods.
  • com.goldencode.p2j.persist.QueryComponent in resolveArg method.
  • com.goldencode.p2j.persist.meta.MetadataManager in addTableToFile, childData, indexData, fieldData and build methods.
  • com.goldencode.p2j.util.AccessorWrapper in get method.
  • com.goldencode.p2j.util.ArrayAssigner in resize and lengthOf method.
  • com.goldencode.p2j.util.DynamicOps in plus, minus and length methods.
  • com.goldencode.p2j.util.TextOps in byteLength, indexOf, lastIndexOf, length, lookup and numEntries methods.
  • com.goldencode.p2j.util.ToClause in 2 constructors.
  • com.goldencode.p2j.util.date in getTimeZone method.
  • com.goldencode.p2j.util.XEntityImpl in getNumChildren method.
  • com.goldencode.p2j.util.XNodeRefImpl in getLocalName method.
Used int64Constant:
  • com.goldencode.p2j.persist.DynamicConversionHelper in injectVariable method.
  • com.goldencode.p2j.persist.QueryComponent in resolveArg method.
  • com.goldencode.p2j.persist.sequence.SequenceHandler in getResultValue, safeGetCurrentValue and safeGetNextValue methods.
  • com.goldencode.p2j.util.ArrayAssigner in assignMulti, assignSingle, assignSingleUnknown and subscriptHelper methods.
  • com.goldencode.p2j.util.BinaryData in length method.
  • com.goldencode.p2j.util.CompareOps in _isEqual, isEqual, _isNotEqual, isNotEqual, _isGraterThan, isGraterThan, _isGreaterThanOrEqual, isGreaterThanOrEqual, _isLessThan, isLessThan, _isLessThanOrEqual and isLessThanOrEqual methods.
  • com.goldencode.p2j.util.DynamicOps in plus and minus methods.
  • com.goldencode.p2j.util.MathOps in abs, random, negate, plus, minus, multiply and modulo methods.
  • com.goldencode.p2j.util.ToClause in 2 constructors.
  • com.goldencode.p2j.util.date in secondsSinceMidnight and elapsed methods.
  • com.goldencode.p2j.util.datetime in 2 constructors.
  • com.goldencode.p2j.util.datetimetz in 4 constructors.

Also committed on 9060a rev. 15671 an empty row deleted by accident and some history entry revisions.
The revisions that need review on the branch: 15659-15671.
Constantin, please review! Thanks.

#30 Updated by Constantin Asofiei over 1 year ago

Review of 9060a rev 15671:
  • CallParameter needs the integer/int64/characterConstant checks
  • CacheManager.java
    • this message Please move it from /server/standard/persistence/cache-size to /server/default/cache-size. - keep in mind that you can have /server/default/ paths. I think the message should be like /server/{default|<serverID>}/persistent/cache-size path - same for the second, @/server/{default|<serverID>}/.
  • character.java, int64.java, integer.java - there are some unused imports, please remove them
  • character - this needs to be inside synchronized: if (CHARACTER_CONSTANTS.size() < CHARACTER_CACHE_LIMIT)
  • int64.of and integer.of - do we need synchronization for DYNAMIC_CACHE?
  • please run javadoc and fix any issues, but only in the files you've already changed

#31 Updated by Ioana-Cristina Prioteasa over 1 year ago

Used Spoon to scan the source code for occurrences of assignments like = MathOps.. Below is the generated report:

==== MathOps ====
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:992)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1024)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1047)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1058)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1069)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1084)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:1088)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/AverageAccumulator.java:404)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:3028)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:3169)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:3174)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/TotalAccumulator.java:394)

==== TextOps ====
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/BufferFieldImpl.java:848)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/BufferImpl.java:9863)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/DataSet.java:4644)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/DataSet.java:4813)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/FieldReference.java:1697)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/Persistence.java:1133)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/QueryWrapper.java:6922)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/RecordBuffer.java:2593)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/orm/OrmUtils.java:138)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/orm/Validation.java:1304)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:966)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:977)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:996)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/BrowseRow.java:182)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/BrowseRow.java:185)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/BrowseRow.java:188)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/BrowseRow.java:189)

==== DynamicOps ====

==== CompareOps ====
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:913)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:921)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:929)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:937)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:946)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/uast/ExpressionEvaluator.java:956)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:170)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:3009)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/ToClause.java:3010)

==== EnvironmentOps ====
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/ColorSpec.java:514)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/ColorSpec.java:519)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/ColorSpec.java:524)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/ColorTable.java:2100)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/Keyboard.java:2833)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/ui/chui/ThinClient.java:3954)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/FileSystemOps.java:1733)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/SourceNameMapper.java:369)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/SourceNameMapper.java:2212)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/SourceNameMapper.java:4425)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/SourceNameMapper.java:4444)
(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/util/memptr.java:210)

The good part is that none of the occurrences are in the oo package. But these could cause issues further down the line if they are assigned.
I attached the code I used to generate this.

#32 Updated by Alexandru Lungu over 1 year ago

Part of my work in #9652 was to analyze performance. To provide some insights into how the BDT instantiation works there:
  • 187k character.<init>(String)
    • 53k from Text.assign
    • 36k from CompareOps.isEqual / _isNotEqual / _isEqual
    • 12k from TypeFactory.character
  • 101k character.<init>(character)
    • 96k from character.duplicate (BDT.copy, RAQ.resolveArg,
    • 4k from GenericFrame.parseValue
  • 224k character.<init>(String, boolean, boolean>
    • 101k from TextOps.instantiate (toUpperCaseImpl, trimImpl, substringImpl, etc.)
    • 101k from Record._getCharacter
  • 88k integer.<init>(Number)
    • 53k from Record._getInteger
    • 29k from UserTableStatUpdater.stat
  • 81k integer.<init>(int)
    • 20k from TypeFactory.integer
    • 15k from TextOps.numEntries
    • 0.5k from FontTable.getTextWidthPixels
    • 0.2k from TextOps.lookup
    • 0.1k from FrameWidget.getDown
  • 92k int64.<init>()
    • mostly from integer c'tors
    • 546 from FontTable.getTextWidthPixels
  • 26k character.<init>()
    • mostly from run-time
    • 3.8k from StringFormat.fromVar4FieldValue
    • 0.5k from StringFormat.fromVar
  • 38k int64.<init>()
    • 20k from CompareOps.isEqual
    • 9.4k from MathOps.minus
    • 3.9k from CompareOps._isEqual
    • 1.9k from CompareOps._isGreaterThanOrEqual
  • 4k decimal.<init>(long)
    • 4.2k from MathOps.plus
  • 4.2k decimal.<init>(BDT)
    • 3.7k from CompareOps.lessThanOrEqual
    • 0.4k from CompareOps.equals
  • 17.5k logical.<init>(boolean)
    • 10k from P2JQuery.getNext()
    • 5.6k from TempTableBuilder.addNewField
    • 0.5k from HandleOps.canQuery
  • 11k logical.<init>(Boolean)
    • 11k from Record._isLogical

#33 Updated by Constantin Asofiei over 1 year ago

Ioana, from the list you posted, I think the only part of concern is:

(/home/icp/projects/branches/9060a/src/com/goldencode/p2j/persist/FieldReference.java:1697)

but the call result = TextOps.toUpperCase(TextOps.rightTrim(c, " ")); will always return a new instance, is not using character.of.

Last commit in 9060a is from Feb 14 - please commit latest changes so we can get this into testing.

As a side note: in the spoon code you posted, you can further filter this for methods which return a BaseDataType; no need to change for now, just an idea if the list is in the 1000's of lines, then always look to reduce them, if possible.

#34 Updated by Ioana-Cristina Prioteasa over 1 year ago

Constantin Asofiei wrote:

  • CallParameter needs the integer/int64/characterConstant checks

CallParameter contains the characterConstant changes and for int and int64 there is this check if (int64.class.isAssignableFrom(type)). I think this is fine?

The rest of the review is addressed and committed in 9060a rev. 15672.

#35 Updated by Constantin Asofiei over 1 year ago

Thanks, looks good. Lets get this into runtime testing.

#36 Updated by Alexandru Lungu over 1 year ago

I updated my list in #9060-32 (now is complete).

#37 Updated by Constantin Asofiei over 1 year ago

Alexandru Lungu wrote:

I updated my list in #9060-32 (now is complete).

TypeFactory instances can't be immutable/constants. The Record methods like Record._isLogical will be worked in a second phase.

Ioana: can you double-check Alexandru's list, to what extent this is covered by 9060a?

#38 Updated by Ioana-Cristina Prioteasa over 1 year ago

From #9060-32:
Already covered by 9060a:
  • TextOps.numEntries and TextOps.lookup
  • CompareOps and MathOps
  • P2JQuery.getNext()
  • TempTableBuilder.addNewField()
New modifications committed in 9060a rev. 15673:
  • UserTableStatUpdater.stat - used integer.of
  • FontTable - used int64.of in more functions
  • FrameWidget.getDown - used integer.of
  • HandleOps.canQuery - used logical constants

I will rebase the branch to facilitate testing, will update when that is done.

#39 Updated by Ioana-Cristina Prioteasa over 1 year ago

9060a is rebased. Revision that need testing 15714-15728:
Please watch out for these error messages:
  • This is an immutable integer instance.
  • This is an immutable int64 instance.
  • This is an immutable character instance.
  • This is an immutable logical instance.

If they appear in the server.log, post it here.

#40 Updated by Ioana-Cristina Prioteasa over 1 year ago

There was again a NPE because the cache was not initialized in time. Was caused by the use of character.of in EnvironmentOps.getOperatingSystem(), it seems like that can get executed before the server hook initializes the CacheManager. Added a null check in character, committed on 9060a rev.15729.

#41 Updated by Constantin Asofiei over 1 year ago

I'm reiterating something Ioana and I discussed about character.of() - at this point in time, the size of this cache is limited, as the strings are interned. So, the domain of the string values used by character.of must be small and also be only from inside FWD runtime, and not from outside, from the application. We may want to try something different (like a LRU) in phase two, but this needs to be kept in mind as we add character.of to more and more places (for example, adding it to Record._getCharacter may do more harm than good).

OTOH, in phase 2 we want to see how we can leverage logical.of and maybe also integer.of - but maybe we should consider if is possible to delay creating the BDT for each record field, and keep Java values as long as possible.

#42 Updated by Eric Faulhaber over 1 year ago

The current implementation uses a simple array for the cache, correct? Keep in mind that an LRU cache will have a lot more overhead, if the cache currently uses only simple indexed access to the array. If the performance margins are slim for this improvement, the speed of the cache could have a big impact.

#43 Updated by Constantin Asofiei over 1 year ago

Eric Faulhaber wrote:

The current implementation uses a simple array for the cache, correct? Keep in mind that an LRU cache will have a lot more overhead, if the cache currently uses only simple indexed access to the array. If the performance margins are slim for this improvement, the speed of the cache could have a big impact.

I'm talking about character.of() which uses an IdentityHashMap as the cache (with a limited max size), so that's not an array. My point with LRU is to do some experiments, what happens if we allow more strings (not interned).

#44 Updated by Alexandru Lungu over 1 year ago

My point with LRU is to do some experiments, what happens if we allow more strings (not interned).

What about the same approach as the dynamic cache in #9060-17? Static array with hashCode as key. The only overhead will be the hashCode computation for the String if not already computed.

#45 Updated by Constantin Asofiei over 1 year ago

Alexandru Lungu wrote:

What about the same approach as the dynamic cache in #9060-17? Static array with hashCode as key. The only overhead will be the hashCode computation for the String if not already computed.

Yes, we can experiment in second phase; but on collision will require an equals.

#46 Updated by Alexandru Lungu over 1 year ago

Yes, we can experiment in second phase; but on collision will require an equals.

I checked H2 code right now and this technique is applied only for strings that do not exceed OBJECT_CACHE_MAX_PER_ELEMENT_SIZE in length (which defaults to 4096). Maybe we can do some experimenting and set a lower margin (?). If set to 16, I expect quite fast equals (also mind the == short-circuit).

#47 Updated by Eric Faulhaber over 1 year ago

Constantin, I believe Ioana said in the stand-up today that 9060a was ready for trunk. If you agree, please queue for merge.

#48 Updated by Constantin Asofiei over 1 year ago

  • Status changed from Review to Merge Pending

9060a can be merged to trunk after 9544a

#49 Updated by Ioana-Cristina Prioteasa over 1 year ago

9060a is merged to trunk as rev.15726 and archived.

#50 Updated by Ovidiu Maxiniuc over 1 year ago

Because of the large update I peek at the changes in r15726.

I think we can squeeze a bit more. There are cases where values like integer.of(0) and int64.of(1) are used. Using the of method and the lookup(s) can be avoided if for these particular cases we define constants. Not many of them, probably 0, 1, 2 for each of the classes is enough. A similar constant I saw already implemented for character.EMPTY_STRING.

At any rate, the way these constants are created makes me a bit nervous. These static constants of a subclass type cannot be created until the current class is fully loaded. So this puts the base for a recursive initialization or some kind of a deadlock. With JDK 8 and 17, it works, but this is a strange construct and possible to fail for other JDKs.

#51 Updated by Artur Școlnic over 1 year ago

I added support for Constant BDTs for getters in Record. There was an issue with the type resolving in FuzzyMethodLookup, but it was fixed.
The code is in 9060b/15754.

#52 Updated by Dănuț Filimon over 1 year ago

  • Status changed from Merge Pending to WIP

#53 Updated by Alexandru Lungu over 1 year ago

  • Assignee changed from Ioana-Cristina Prioteasa to Artur Școlnic

On today meeting on #9060:

  • Extend caching to more BDT when possible. Consider using dynamic caching for this. Refer to #9060-17.
    • This may be a costly operation for character as hashing String it is costly, but can fit decimal, date or other types that have very light hashing. Take example from integer and let that dynamic cache configurable.
  • For records: getters should return immutable types.
    • Create a "constant" version for each BDT. For this, use .unmodifiable wrapping static method to return a constant version of the BDT.
    • As an invariant, "of" is going to return an immutable instance after attempting to cache it (or look-up in a cache), while "unmodifiable" is simply returning a constant without any caching work.
    • I think for logical, integer ,etc. we can use of, but more tricky types like character should use unmodifiable.
  • Save the BDT to Record level lazily using a separate array to store them. Consider invalidation when necessary (when altering data due to setters).
  • Improve character caching.
    • Currently it is used in a very specific place and the characters are interned. I don't have an immediate recommendation for this, so we should let the profiling generate some leads to drive any effort in this area.
  • Conversion changes to replace new unknown() with unknown singleton where possible.
  • Conversion changes to generate literals with .of method instead of new construct.

I think we can tackle these in this order. For conversion, feel free to open a separate task in conversion as the work on that shall come last and affect code in TRPL, rather than run-time.

#54 Updated by Artur Școlnic over 1 year ago

Should every constructor for a BDT have an equivalent immutable() overload?

#55 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Should every constructor for a BDT have an equivalent immutable() overload?

Add a static unmodifiable(<java native value>) to all BaseDataType sub-classes - get the list of all valid data type for a database or temp-table field (what the getter types in Record are).

#56 Updated by Artur Școlnic over 1 year ago

What I meant is that we only use one or two constructor overloads in Record for each BDT, so it makes sense to have an equivalent (the same parameters) immutable method only for those and not for all the constructors.

#57 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

What I meant is that we only use one or two constructor overloads in Record for each BDT, so it makes sense to have an equivalent (the same parameters) immutable method only for those and not for all the constructors.

Ah, yes, that's good, add overloads as needed.

So we go with immutable or unmodifiable name for this new method?

#58 Updated by Artur Școlnic over 1 year ago

Immutable objects can't be changed at all, unmodifiable objects can't be change externally, but their state can be modified internally.

#59 Updated by Alexandru Lungu over 1 year ago

Artur Școlnic wrote:

Immutable objects can't be changed at all, unmodifiable objects can't be change externally, but their state can be modified internally.

  • if we generate a "wrapper" over an existing BDT, then we should go with unmodifiable, as it is a term used by Java collection wrappers (e.g. Collections.unmodifiableList). This intercepts calls to assign like a proxy and rules out any modification.
  • if we generate objects of a new class that explicitly overrides the "assign" method, then immutable is the right terminology. However, there is absolutely no occurence of an "immutable" method in Java docs.
    • Java 9 onward defines of static method as a way to provide immutable variants, mostly like we attempted with FWD. Thus, my personal preference is to stick with of and use caching whenever possible. If this is not possible (like for characters), lets have a flag for that (i.e. honorStaticCache that will eventually do an intern and use the cache). The default for characters is honorStaticCache = false.

#60 Updated by Constantin Asofiei over 1 year ago

Alexandru Lungu wrote:

  • Java 9 onward defines of static method as a way to provide immutable variants, mostly like we attempted with FWD. Thus, my personal preference is to stick with of and use caching whenever possible. If this is not possible (like for characters), lets have a flag for that (i.e. honorStaticCache that will eventually do an intern and use the cache). The default for characters is honorStaticCache = false.

I agree with this; lets go with .of() and an optional flag for i.e. character which would not cache the instance. This way, if we add future caches for types like date(time(tz)), then we don't need to change the Record getters.

#61 Updated by Andrei Plugaru over 1 year ago

Regarding the interference of this task to lazy hydration #6720.
IMO, at least for the current work on this task, we shouldn't have any conflicts with that. My only concern was if the data array from Record was directly accessed. Currently, it is not. However, Artur will probably need to access it in the future, and he will probably avoid using getDatum due to performance reasons. So, we should only take that into account when lazy hydration will get merged.

I will continue watching this task and if I see something that mixes with lazy hydration, I will notify about that.

#62 Updated by Artur Școlnic over 1 year ago

  • Status changed from WIP to Review

I added the immutable types and used them in Record getters, so far it seems to work fine. The code is in 9060b/15755. I think a review after each phase would be an easier approach rather that analyzing all the changes at the end.

#63 Updated by Constantin Asofiei over 1 year ago

Review for 9060b/15755:
  • fuzzyMethodLookup - fromJava already has fromConstantType, I think toLegacyBDT call of fromConstantType will do nothing.
  • fromConstantType - instead of 'indexOf', use 'endsWith', and after that a substring using the "Constant".length()
  • why the access mode change for some methods in i.e. datetimetz?
  • handle - instead of duplicating code in handleConstant for fromResourceId, get the common code into a method which returns a WrappedResource and call.
  • rawConstant.setUnknown() - this breaks the 'immutable' part - if we cache them, then there will be problems.
  • recid is missing the overrides to make it immutable
  • look how logicalConstant is checked througout the project - same must be done for the new constant types.
  • look into the implementations of DataHandler - at least persist.orm.types.NumericType.convert(Object) I think needs to create constants, too.
  • why using recid.ofRecid?
  • please place the public static 'of' method at the beginning of the file, after constructors

I have not checked if overrides are OK for all constant types.

#64 Updated by Artur Școlnic over 1 year ago

Constantin Asofiei wrote:

Review for 9060b/15755:
  • why the access mode change for some methods in i.e. datetimetz?

I needed the methods for constant types constructors.

  • rawConstant.setUnknown() - this breaks the 'immutable' part - if we cache them, then there will be problems.

This is extensively used in FWD, not allowing it, breaks the project. For now, I do not have a solution.

  • recid is missing the overrides to make it immutable

recid does not have any methods that alter it's state.

  • why using recid.ofRecid?

There is a conflict with the of from integer.

#65 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

  • rawConstant.setUnknown() - this breaks the 'immutable' part - if we cache them, then there will be problems.

This is extensively used in FWD, not allowing it, breaks the project. For now, I do not have a solution.

Please provide some examples.

#66 Updated by Artur Școlnic over 1 year ago

  • Status changed from Review to WIP

Constantin Asofiei wrote:

Please provide some examples.

What I meant is that it cannot be private or protected, as it is used a lot in FWD. The issue was that setUnknown is used in super constructors, but on a second look I managed to work around it, everything is working fine now with setUnknown being restricted. The next commit will have the fixes for the issues pointed out in the review and the caching of the BDTs in BaseRecord, I am pretty close with it.

#67 Updated by Artur Școlnic over 1 year ago

  • Status changed from WIP to Review

9060b/15764 is ready for review.

#68 Updated by Artur Școlnic over 1 year ago

Clearly a LRU cache is not a good idea for caching the BDT constants, but what about a Most Frequently Used (MFU) cache?
The insertion, access and eviction operations can be done in constant time O(1), the cache size can be configured to be a relatively small one, as to not consume too much memory. This would not be as quick as the current static caching done in integer, but is a viable solution for the more complex types.

#69 Updated by Alexandru Lungu over 1 year ago

Artur Școlnic wrote:

Clearly a LRU cache is not a good idea for caching the BDT constants, but what about a Most Frequently Used (MFU) cache?
The insertion, access and eviction operations can be done in constant time O(1),

MFU is a cache policy which doesn't imply a certain implementation. Please describe how is this going to be implemented: the data structures and algorithm involved.

the cache size can be configured to be a relatively small one, as to not consume too much memory.

Memory is the last one to care about if the performance is improved. We can afford to spend extra 1MB per server for 1% time improvement (on a consistent manner and without synchronization overhead).

#70 Updated by Artur Școlnic over 1 year ago

This is the most efficient implementation I found:

Key Data Structures:
  • HashMap<K, V>: Stores the actual cache data.
  • HashMap<K, Integer>: Tracks the frequency for each key.
  • HashMap<Integer, LinkedHashSet<K>>: Groups keys by their access frequency. The frequency count is the key, and the values are sets of keys that have that frequency.
  • minFrequency: Keeps track of the least frequently used frequency to easily find the entry to evict.
Key Operations:
  • get(K key):
    Retrieves the value for the specified key.
    Increments the access frequency of the key.
    Moves the key to the appropriate frequency list.
  • put(K key, V value):
    Adds a new key-value pair to the cache.
    Initializes the frequency of the key to 1.
    Adds the key to the frequency list for frequency 1.
    Updates the minFrequency to 1 after insertion.
  • Eviction:
    If the cache exceeds its capacity, it evicts the least frequently used entry (from the minFrequency list).
    The evicted entry is removed from both the cache and the frequency map.

#71 Updated by Artur Școlnic over 1 year ago

The general idea is that the least used entries will always change, but the most used one will always be "in the top" of the cache, so if a lot of the same values are used, there won't be a lot of adding/eviction operations.

#72 Updated by Alexandru Lungu over 1 year ago

Artur Școlnic wrote:

This is the most efficient implementation I found:

I am not sure if this is more performant than a LRU cache, which uses a Map and a Linked List. The complexity of LRU operations is also O(1), but the practical constant is high (comparing to a simple array). We may agree that MFU can reach a similar constant to the LRU, but it has the same flaw as the LRU in the first place - which is high :)

For example, an arbitrary LRU operation is consuming 200 time units (which is O(1)) and the MFU operation is consuming 180 time units at best (which is also O(1)). In both cases, we may end up paying a time price higher than 20 time units that is memory allocation + GC stress. The #9060 goal is to check for a ... lets say ... 3 time units approach (which is again O(1)). Having this very narrow space forces us to use very trivial solutions. Very brute example: based on array look-up (2 time units) + one arithmetic operation (1 time unit). Simply invoking the get method, passing down parameters, hashing, returning from stack, etc. adds to the total of time units.

You can consider these "time units" as ms, opcodes executed, bytes accessed, or whatever time metric you see fit. All of the values in the example are simply illustrative and have no real experimentation behind - are there just to prove a point.

#73 Updated by Artur Școlnic over 1 year ago

I did a memory profiling on a large application FWDTest and saw that the most instantiated BDTs are unknown, and there are not any actual highly used values that could be statically cached for a performance improvement. Most likely, the conversion change we discussed and the transition to UNKNOWN static constant in the project where it make sens, will decrease greatly the number of instantiated BDTs.

#74 Updated by Artur Școlnic over 1 year ago

Memory profiling with the changes in 9060b results for BDT instances (large application FWDtest that uses a lot of CRUD operations):

object 1.4 million -> 300k (-78.57%)
datetimetz 15k -> 10k (-33.33%)
datetime 7.3k -> 5.8k (-20.55%)
date 33k -> 25k (-24.24%)
rowid 3.2k -> 2.1k (-34.38%)
handle 64k -> 50k (-21.88%)
decimal 280k -> 220k (-21.43%)
raw 5.8k -> 3k (-48.28%)

Depending on the scenario, BDT instance creation could be significantly reduced, I suppose most of it is due to the usage of UNKNOWN constants.

I committed rev 15765, character caching in Record was added, still, new instances are used in the getter as trying to use constants, even the UNKNOWN, brings huge regressions.

#75 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

I committed rev 15765, character caching in Record was added, still, new instances are used in the getter as trying to use constants, even the UNKNOWN, brings huge regressions.

Are you saying you are not using characterConstant for record's character getter?

#76 Updated by Artur Școlnic over 1 year ago

At this point, no. I am working on it.

#77 Updated by Artur Școlnic over 1 year ago

Currently I am trying to solve this issue:

 new InputOutputExtentField(tempTable, "field") // field is of character type

The values for the parameters will be constants, because the InputOutputExtentField constructor invokes the character getter.
Later the parameter is passed to another method which assigns values to it, naturally, this breaks the immutability and the scenario crashes.

#78 Updated by Artur Școlnic over 1 year ago

Maybe we can cast the constant types to normal ones in FieldReference.getObject?

#79 Updated by Artur Școlnic over 1 year ago

Artur Școlnic wrote:

Maybe we can cast the constant types to normal ones in FieldReference.getObject?

Or pass a parameter to it, to indicate whether we want an immutable instance or not.

#80 Updated by Constantin Asofiei over 1 year ago

I think the place is in InputOutputExtentField.

And also for InoutOutputField?

#81 Updated by Artur Școlnic over 1 year ago

Constantin Asofiei wrote:

And also for InoutOutputField?

It is possible, so far I have been solving issues as they arise.
So casting in such cases is ok?

#82 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Constantin Asofiei wrote:

And also for InoutOutputField?

It is possible, so far I have been solving issues as they arise.
So casting in such cases is ok?

So there is no InputOutputField class. But, my point is: when you find a problem, look for the pattern: in this case, is INPUT-OUTPUT <field-ref>, which may be extent or not. Create standalone tests and check how it behaves.

For the InputOutputExtentField - yes, that is the place where to transform from constants to BDT. I don't see a simpler solution, as INPUT-OUTPUT means in 4GL that the argument is passed 'as reference' (be it a variable, field, property, etc).

#83 Updated by Artur Școlnic over 1 year ago

Alexandru suggested that at some point we should create new mutable instances and return them in the case of InputOutputExtentField.

#84 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Alexandru suggested that at some point we should create new mutable instances and return them in the case of InputOutputExtentField.

Yes, that's what I mean with 'transform from constants to BDT' - get an array with mutable instances instead of constants, in InputOutputExtentField.

#85 Updated by Artur Școlnic over 1 year ago

I encountered a few issued when using constant character, one of them leaves some tests in a perpetual wait state, a code example is illustrated below, it is inspired by the code in one of our customer's test:

define temp-table tt1 fields f1 as char.
define temp-table tt2 fields f2 as char.
create tt1.
tt1.f1 = "100".
create tt2.
substring(tt2.f2,1,2) = right-trim(string(tt1.f1, "-999,999,999.99999")). 
message tt2.f2.

is converted to:
RecordBuffer.openScope(tt1, tt2);
tt1.create();
tt1.setF1(new character("100"));
tt2.create();
tt2.setF2(tt2.getF2().replace(rightTrim(valueOf(tt1.getF1(), "-999,999,999.99999")), 1, 2));
message((character) new FieldReference(tt2, "f2").getValue());

Although the result in FWD is the same as in 4GL, the tt2.setF2(tt2.getF2() code is troubling me. Now the replace is permitted in trunk, but I intend to restrict it in characterConstant, so in the future, code like that will fail. Most likely this will need to be changed at conversion.

As a side note, 130 attempts to use replace or replaceEntry with characterConstant are made by the FWDTests and some of them are failing due to this error.

Other than that, I managed to make it work and committed the code as rev 15766.

#86 Updated by Artur Școlnic over 1 year ago

After running 106 FWDTests, these are the results of the dynamic cache usage:

95% of decimals are retrieved from the cache.
17% of the characters are retrieved from the cache. 
63% of the date are retrieved from the cache. 
50% of the datetimetz are retrieved from the cache. 
Other types have a low occurance or extremely high miss rate.

From the tests I conduceted, I would say it is definetely worth it to enble the dynamic caching for decimal, for character, I would avoid caching.
For date and datetimetz I conducted a series of experiments. I generated 1 mil values and recorded the total execution time, including garbage collection.
Creating 1 mil datetimetz instances takes, on average, 6594 milliseconds, using the dynamic cache, that has a hit rate of 50%-60%, takes 9140 milliseconds, this is a 71% total execution time increase, so I did not include the dynamic caching for these types.

I committed the code that enables dynamic caching for decimalConstants as rev 15767.

#87 Updated by Alexandru Lungu over 1 year ago

Creating 1 mil datetimetz instances takes, on average, 6594 milliseconds, using the dynamic cache, that has a hit rate of 50%-60%, takes 9140 milliseconds, this is a 71% total execution time increase, so I did not include the dynamic caching for these types.

This is odd. Can you share the caching code for datetimetz?

#88 Updated by Artur Școlnic over 1 year ago

 if (DYNAMIC_CACHE == null)
      {
         return new datetimetzConstant(odt);
      }
      // Check dynamic cache
      int index = Math.abs(odt.hashCode() % DEFAULT_DYNAMIC_CACHE_SIZE);

      synchronized (DYNAMIC_CACHE)
      {
         datetimetzConstant cachedInstance = DYNAMIC_CACHE[index];
         if (cachedInstance != null && odt.isEqual(cachedInstance.offsetDateTimeValue()))
         {
            return cachedInstance;
         }
         // Cache miss - create and replace the entry in dynamic cache
         datetimetzConstant newInstance = new datetimetzConstant(odt);
         DYNAMIC_CACHE[index] = newInstance;
         return newInstance;
      }

Of course, writing a performance test for such cases is not trivial, so there is a chance that the results look weird due to the testing methodology. I welcome any suggestions for testing the dynamic caching in isolation.

#89 Updated by Alexandru Lungu over 1 year ago

Extra ideas:
  • CompareOps.equals can use a == short-circuit now, as there is a higher chance to have the same BDT as operands. For example tt.f1 = 1 can be short-circuited as the static cache is used. The same applies to tt.f1 = 1.1 as the dynamic cache is used.
  • System.identityHashCode can be used for String to retrieve a unique identifier for them (similar to a hash-code, but I guess it is faster). As for the equals check we can use == instead. This way, we can provide the same character instances for same String instances. This can help for strings literals that are interned by default. I don't know the performance cost of identityHashCode ... this should be investigated.

#90 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Of course, writing a performance test for such cases is not trivial, so there is a chance that the results look weird due to the testing methodology. I welcome any suggestions for testing the dynamic caching in isolation.

Test the cache directly in Java - create the datetime-tz strings (what is odt in your code?), save them in a list, and after that check how .of() behaves when giving it this list.

#91 Updated by Artur Școlnic over 1 year ago

Constantin Asofiei wrote:

Test the cache directly in Java - create the datetime-tz strings (what is odt in your code?), save them in a list, and after that check how .of() behaves when giving it this list.

This is what I have been doing. The 70% performance decrease is the result of tests like that.

#92 Updated by Artur Școlnic over 1 year ago

I am trying to replace all date instance creation in the project with the date.of. Naturally this breaks everything, should I leave the date instances that are used outside FWD as is?
For example:

date someDate = TypeFactory.date();// should return date or dateConstant?

#93 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

I am trying to replace all date instance creation in the project with the date.of. Naturally this breaks everything, should I leave the date instances that are used outside FWD as is?
For example:
[...]

TypeFactory and UndoableFactory must return mutable instances - this is how vars are defined in the converted 4GL code.

#94 Updated by Artur Școlnic over 1 year ago

I suppose BaseDataTypeFactory.instantiate and any BDT generation method should return a mutable instance.

#95 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

I suppose BaseDataTypeFactory.instantiate and any BDT generation method should return a mutable instance.

Not necessarily. BaseDataTypeFactory is used only internally in FWD - are the usages requiring to have a mutable instance?

#96 Updated by Artur Școlnic over 1 year ago

It seems so, I am getting errors in the SharedVariableManager because dateConstants are used.

#97 Updated by Artur Școlnic over 1 year ago

The issue seem to originate from BaseDataType.sameType, it returns false for date and constant. Does it make sens to alter it so it returns true?

#98 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

The issue seem to originate from BaseDataType.sameType, it returns false for date and constant. Does it make sens to alter it so it returns true?

Why would a constant end up in the SharedVariableManager registries? That's the root cause IMO.

#99 Updated by Artur Școlnic over 1 year ago

Object var = (extent != null ? Array.newInstance(expected, Math.max(0,  extent))
                                   : type != null ? new object(type) 
                                                  : BaseDataType.generateDefault(expected));

generateDefault generates a date.UNKNOWN, that's why I asked about it.

#100 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

[...]
generateDefault generates a date.UNKNOWN, that's why I asked about it.

Because of BaseDataTypeFactory, right? If so, leave BaseDataTypeFactory unchanged for now.

#101 Updated by Artur Școlnic over 1 year ago

Replacing new date with date.of throughout the project means there are going to be a lot of overloads for the of, which means a lot of overloaded constructors for dateConstant. A lot of them use restricted methods (assign, setUnknown...), would it be acceptable to create private versions of these methods? This would mean duplication of code, but the alternative would be to duplicate it directly in the constructors, which also requires access to parent's attributes.

#102 Updated by Alexandru Lungu over 1 year ago

Artur, can you do a safe cut-out of the current changes you have to merge to trunk by EOW? Other matters can be deferred in the next iteration.
I really want the decimal dynamic cache (Urgent).

#103 Updated by Artur Școlnic over 1 year ago

  • Status changed from Review to WIP

I will bring the branch to a stable state by solving the issue described in #9060-85 and any other that pop out during testing. Replacing new instance creation with constants, dynamic caches for date/datetimetz and the conversion change that generates constants instead of new instance creation will be deferred to 9060c.

#104 Updated by Artur Școlnic over 1 year ago

Here are my proposals for solving the replace issue:

original: tt2.setF2(tt2.getF2().replace(rightTrim(valueOf(tt1.getF1(), "-999,999,999.99999")), 1, 2));

option 1: tt2.setF2(new character(tt2.getF2()).replace(rightTrim(valueOf(tt1.getF1(), "-999,999,999.99999")), 1, 2));
// wrap the constant in a new char and use repalce on it.

option 2: tt2.setF2(tt2.getF2().substring(rightTrim(valueOf(tt1.getF1(), "-999,999,999.99999")), 1, 2));
//add a new method, substring returns a new immutable instance 

option 3: tt2.setF2(tt2.getF2().replace(rightTrim(valueOf(tt1.getF1(), "-999,999,999.99999")), 1, 2));
// check in the method if it was generated at conversion time, if true, allow altering the state of the constant. At 'runtime', throw the error, if that can be done...

The same approach would need to be applied to the replaceEntry method.

Constantin, Alexandru, I would appreciate your input on this.

#105 Updated by Artur Școlnic over 1 year ago

Of course I will try to limit the changes to be applied only in the field access scenario.

#106 Updated by Artur Școlnic over 1 year ago

I had a talk with Constantin, the conclusion was that we need to wrap in mutable instances the constant types for every assign style statement.

#107 Updated by Artur Școlnic over 1 year ago

  • Status changed from WIP to Review

I committed the conversion change to rev 15768, Constantin, please take a look. I converted hotel and there were no changes. I think all the changes currently in the branch need to be reviewed, after that I can proceed with an extensive testing plan.

#108 Updated by Constantin Asofiei over 1 year ago

  • reviewer Alexandru Lungu added
Review for 9060b rev 15768:
  • in database_references.rules, I think you need to check for the index, too
  • BufferImpl - are all characterConstant case-insensitive? From Record._getCharacter, this is not the case - so I think there we need to create a character mutable instance with case-sensitive = false.
  • CacheManager requires history entry
  • Record
    • _getCharacter - please align the arguments in character.of
    • _getRecid does not use cachedBDTs
    • setPropertyValues - look for direct write access of BaseRecord.data field
  • FuzzyMethodLookup - fromConstantType - instead of contains use endsWith
  • character$characterConstant.equals was removed?
  • look how logicalConstant or integerConstant is used - you need to register all new constant types in the same places

Alexandru: please take a look, too.

#109 Updated by Alexandru Lungu over 1 year ago

Review of 9060b:

  • Leave a space between the target and cast type (e.g. datetimetz result = (datetimetz)cachedBDTs[offset]; should be datetimetz result = (datetimetz) cachedBDTs[offset];)
  • _getCharacter doesn't seem to cache unknown values.
  • There are spots in BaseRecord that still should invalidate the cache (e.g. copy(BaseRecord from), RecordMeta.applyInitialValues).
    • I think *Type.java classes are all changing the record's data array. For example, BlobType.setField is setting a field in the data and so it should invalidate the cached BDT.
    • Please look more in depth on this, I barely skimmed some places that need invalidation. Omitting some of this may be fatal as updates won't be visible as the cached value will be old.
  • move static unknown constants at the top of properties (before the instance props).
  • put overloaded methods one near each other (the one will less args before the one with more props): see character.of
  • Isn't assign(value.val()); from clobConstant going to throw an error due to immutability?
  • All .of variants may rather return UNKNOWN if the parameter is null. This better encapsulates the null-safety.
  • Some braces are placed on the same line as the method signature (e.g. dateConstant(Date d) {)
  • Misalignments:
    +   public static class datetimetzConstant
    +           extends datetimetz
    +           implements BaseDataTypeConstant
  • Leftovers:
    long startTime = System.currentTimeMillis(); // Start time
  • I am quite nervous with comparing double values with ==: cachedInstance.doubleValue() == value.doubleValue(). This may be not accurate due to limitations of double data type precision. Can we rather have cachedInstance.equals(value)? Please do the required tests to confirm we still have the same cache hit rate.
  • I wonder if the synchronization is mandatory. What can happen if we remove that? The concurrent access is highly improbable on that and we pay a cost of synchronization for each decimal created. If there is actual concurrent access, if these are on different spots, I suspect nothing wrong can happen. If they are on the same position, a shadow update will happen so only one value will prevail in the cache, but two threads may work with two different instances. This is a very highly improbable scenario that will cost us only 1 extra instance creation. The cost of synchronizing however is too big!
    • Please do the required testing and if possible get rid of the synchronized block (from integer as well). Redo tests for date and other types you seem fit. Maybe the overhead was from synchronization.
  • In object: res.assign(object.of(datum)); seems like the constant is redundant as it is assigned straight-away to a mutable instance that ends up being cached. Should the res simply be assigned with = operator to the of return value?

#110 Updated by Artur Școlnic over 1 year ago

Constantin Asofiei wrote:

Review for 9060b rev 15768:
  • in database_references.rules, I think you need to check for the index, too

I am not sure what you mean, the index of the property? Check it for what?

  • character$characterConstant.equals was removed?

Yes, it was causing the unsupported exception. Is there a reason that characterConstant can't use the parent's method?

  • look how logicalConstant or integerConstant is used - you need to register all new constant types in the same places

Do you mean replace new instance creation with constants in the project? If so, that would be the next step, which I already partly implemented, but for now we need to wrap up the existing changes.

#111 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Constantin Asofiei wrote:

Review for 9060b rev 15768:
  • in database_references.rules, I think you need to check for the index, too

I am not sure what you mean, the index of the property? Check it for what?

Check the position of the field property in the AST - it should be zero I think?

  • character$characterConstant.equals was removed?

Yes, it was causing the unsupported exception. Is there a reason that characterConstant can't use the parent's method?

You mean Can not compare character with characer constant!? Or that now a constant and a mutable character can co-exist in the FWD runtime, so i.e. compare needs to be OK?

  • look how logicalConstant or integerConstant is used - you need to register all new constant types in the same places

Do you mean replace new instance creation with constants in the project?

No, I mean where the i.e. logicalConstant.class is checked beside logical.class.

If so, that would be the next step, which I already partly implemented, but for now we need to wrap up the existing changes.

Is mandatory to make these changes in the branch, it can't be merged without them.

#112 Updated by Artur Școlnic over 1 year ago

Constantin Asofiei wrote:

You mean Can not compare character with characer constant!?

Yes.

#113 Updated by Alexandru Lungu over 1 year ago

Is mandatory to make these changes in the branch, it can't be merged without them.

I tend to agree with this statement. As long as we throw "constant" BDT into the wild, we need to ensure they are properly supported by the rest of the FWD API. So indeed, jobject.toJavaInstance is one of the many examples.

#114 Updated by Artur Școlnic over 1 year ago

Artur Școlnic wrote:

that would be the next step, which I already partly implemented, but for now we need to wrap up the existing changes.

I was talking about using the constant types in the project instead of creating new instances. Class checking of the BDTs though the project, indeed needs to be complemented with the new constant types checking, I will add this in the next revision.

#115 Updated by Artur Școlnic over 1 year ago

Alexandru Lungu wrote:

  • I am quite nervous with comparing double values with ==: cachedInstance.doubleValue() == value.doubleValue(). This may be not accurate due to limitations of double data type precision. Can we rather have cachedInstance.equals(value)? Please do the required tests to confirm we still have the same cache hit rate.
  • I wonder if the synchronization is mandatory.

Removing the synchronization did not affect the hit rate much, but using cachedInstance.equals(value) simply breaks the caching because it is always false.

#116 Updated by Alexandru Lungu over 1 year ago

but using cachedInstance.equals(value) simply breaks the caching because it is always false.

  1. why?
  2. can you attempt compareTo() == 0

#117 Updated by Artur Școlnic over 1 year ago

  • Status changed from Review to WIP

Alexandru Lungu wrote:

  1. why?

Isn't this expected? Comparing a decimalConstant with a BigDecimal using equals should not return true.

  1. can you attempt compareTo() == 0

This seems to work fine, the hit rate is not affected.

#118 Updated by Alexandru Lungu over 1 year ago

Isn't this expected? Comparing a decimalConstant with a BigDecimal using equals should not return true.

Oh, sorry for the confusion. I meant cachedInstance.value.equals(value) or something like that: comparing the BigDecimal of the cached instance with the BigDecimal provided, without converting to double and use ==.

#119 Updated by Artur Școlnic over 1 year ago

Alexandru Lungu wrote:

Oh, sorry for the confusion. I meant cachedInstance.value.equals(value) or something like that: comparing the BigDecimal of the cached instance with the BigDecimal provided, without converting to double and use ==.

Only compareTo seems to work as expected.

#120 Updated by Alexandru Lungu over 1 year ago

Please make sure that compareTo returns 0 only when the scale is the same. Otherwise, we may end up considering def var x as decimal scale 2 initial 1 and def var y as decimal scale 3 initial 1 equal, although these should be different decimal instances.

#121 Updated by Artur Școlnic over 1 year ago

decimal.compareTo uses BigDecimal.compareTo which checks the scale, so I think we are safe.

#122 Updated by Artur Școlnic over 1 year ago

I started a more extensive testing process, hundreds of unit tests are failing because of assign style methods being used with a BDT that was retrieved with a getter, so it is a constant. The most notable example is clob.write. Should I wrap the constant given as a parameter in a new mutable type and return it instead?

#123 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

I started a more extensive testing process, hundreds of unit tests are failing because of assign style methods being used with a BDT that was retrieved with a getter, so it is a constant. The most notable example is clob.write. Should I wrap the constant given as a parameter in a new mutable type and return it instead?

Didn't we solved this via conversion? Did you reconvert with 9060b?

#124 Updated by Artur Școlnic over 1 year ago

This is different, for example:

at com.goldencode.p2j.util.clob$clobConstant.assign(clob.java:705)
at com.goldencode.p2j.util.Text.assign(Text.java:2335)
at com.goldencode.p2j.util.clob.write(clob.java:443)
at com.goldencode.p2j.util.TargetLob.write(TargetLob.java:382)
at com.goldencode.p2j.util.LobCopy.run(LobCopy.java:333)

LobCopy ends up writing data in the clob object, which could be constant, so in situations like this, I am inclined to wrap the constant in a new BDT so the write method does not use the same instance to assign the new data.
The converted code looks like:
new LobCopy(new SourceLob(longchar), new TargetLob(new FieldReference(tt, "field"))).run(); 

This is just an example, the idea is to come up with a general approach for this kind of situations where the immutable types end up being used in FWD with state altering methods.

#125 Updated by Alexandru Lungu over 1 year ago

new LobCopy(new SourceLob(longchar), new TargetLob(new FieldReference(tt, "field"))).run();

I agree that public TargetLob(FieldReference field) should wrap the constant in a mutable instance as TargetLob.lob is an internal longchar used to store the destination value. On TargetLob.write, if a field exists, the field is set with this internal BDT! Thus, the lob property seems to be an "intermediate" place to store that.

Please do extra tests with memptr, blob and clob data types to ensure they are also properly handled by the LobCopy operation (after fixing the longchar case).

#126 Updated by Constantin Asofiei over 1 year ago

Artur, BTW, clobConstant needs to override all methods which write it, like the clob.write(clob.java:443).

#127 Updated by Constantin Asofiei over 1 year ago

Artur, please go through all Accessor.get() references - there only 64 I can find, and at least Stream.readField looks like it needs attention. And there, I don't understand this code:

            if (ref.getType() == blob.class)
            {
               var.assign(PropertyMapper.preprocessBlob(new character(var)));
            }
            else if (ref.getType() == clob.class)
            {
               var.assign(PropertyMapper.preprocessClob(new character(var)));
            }
            else
            {
               ref.set(var);
            }

ref is a field, but in case of blob or clob, the var gets assigned, insead of ref.set(var).

#128 Updated by Artur Școlnic over 1 year ago

  • Status changed from WIP to Review

I committed 9060b/15771, it contains fixes for issues from the review, constant type registering throughout the project and general regression fixing as a result of running a large application unit test suite.

Regarding public void readField(FieldReference ref), maybe I am mistaken, but I don't think it is used anywhere, otherwise I would have seen errors in it during testing. It looks like it calls itself recursively and no other methods call it.

#129 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

I committed 9060b/15771, it contains fixes for issues from the review, constant type registering throughout the project and general regression fixing as a result of running a large application unit test suite.

Regarding public void readField(FieldReference ref), maybe I am mistaken, but I don't think it is used anywhere, otherwise I would have seen errors in it during testing. It looks like it calls itself recursively and no other methods call it.

See this test:


def temp-table tt1 field f1 as int.
create tt1.
import tt1.f1.

readField(FieldReference) is emitted at conversion.

#130 Updated by Artur Școlnic over 1 year ago

The changes in rev 15771 should solve the immutable types problem

BaseDataType var = ref.get();
var = readField(var); // readField returns a muttable BDT

so the execution will proceed as it did before the changes.
The only change that could be needed is in the conditional you mentioned in #9060-127.

if (ref.getType() == blob.class)
{
   var.assign(PropertyMapper.preprocessBlob(new character(var)));
}
else if (ref.getType() == clob.class)
{
   var.assign(PropertyMapper.preprocessClob(new character(var)));
}
ref.set(var);

I believe clob/blob data should be preprocessed, assigned to var which then should be set to the field reference.

#131 Updated by Constantin Asofiei over 1 year ago

Please create some tests to check IMPORT with blob/clob.

#132 Updated by Artur Școlnic over 1 year ago

I did the tests, with 9060b, both export and import is broken, with trunk, export is ok, but the import is broken. I am working on a fix.

#133 Updated by Artur Școlnic over 1 year ago

So I think PropertyMapper.preprocessClob/Blob is necessary because it assigns to var the actual data of the lob, as opposed to ref.get() which assign the name/path of the lob file, so the preprocessing is vital, the problem is that it is broken, PropertyMapper.readLobData is failing because it relies on the ImportWorker to be initialized, but it isn't.

#134 Updated by Artur Școlnic over 1 year ago

I see that Eric and Ovidiu worked on ImportWorker.initialize, can someone please explain where should it be called, it is needed to properly locate the lob files, which at this moment is not happening.

#135 Updated by Dănuț Filimon over 1 year ago

Artur Școlnic wrote:

I see that Eric and Ovidiu worked on ImportWorker.initialize, can someone please explain where should it be called, it is needed to properly locate the lob files, which at this moment is not happening.

It's used in the rules, check rules/schema/import.xml.

#136 Updated by Constantin Asofiei over 1 year ago

Dănuț Filimon wrote:

Artur Școlnic wrote:

I see that Eric and Ovidiu worked on ImportWorker.initialize, can someone please explain where should it be called, it is needed to properly locate the lob files, which at this moment is not happening.

It's used in the rules, check rules/schema/import.xml.

And ImportWorker must not be used by runtime

#137 Updated by Artur Școlnic over 1 year ago

Well, PropertyMapper uses it:

   private static byte[] readLobData(String locator)
   {
      String path = ImportWorker.lobPath; // lobPath is null
      if (!path.endsWith(File.separator))

At this point we have the lob file name, but not the path.

#138 Updated by Constantin Asofiei over 1 year ago

OK, then please create a task in the database project for this. The code may be used just for importing the .d data (and lobs), not for IMPORT statement at runtime.

#139 Updated by Artur Școlnic over 1 year ago

  • Related to Bug #9872: Runtime import statement relies on the ImportWorker to read lob data. added

#140 Updated by Constantin Asofiei over 1 year ago

Review of 9060b rev 15771:
  • FieldReference
    • is missing history entry
    • do we need to do the same for blobConstant?
  • RecordMeta
    • is missing history entry
  • RecordLoader
    • is missing history entry
  • characterConstant
    • you added javaSpacifyNull, but you always do value = javaSpacifyNull(value); if there is a value. This seems wrong.
  • clobConstant
    • the if (BaseDataType.isProxy(value)) is always false, because you do Text value = new character(str);
    • why the checkUndoable(value); call? This is an immutable instance.
    • writeExternal should be allowed I think.
  • decimal
    • DYNAMIC_CACHE is global per JVM. We need synchronization here, I don't think volatile is enough.
  • DynamicOps
    • is missing history entry
    • what were the reasons for this change? Now you return the actual BDT instance (from the args) as the result, and not a copy; this is wrong.
  • ObjectOps
    • I assume this change is related to delete object tt1.objectField; please do o.ref = null; only if the instance is an objectConstant.
               if (deleted)
               {
                  o.ref = null;
               }
      
  • recidConstant
    • shouldn't this override all methods as in integerConstant?
  • TargetLob
    • is missing history entry
    • this.lob = new clob(field.get()); line is wrong. A field can be memptr also.
  • Stream
    • what is the test for the readField(BaseDataType[] var) change?
  • please run ant javadoc and fix any javadoc problems in the files changed in this task.
  • data import needs to be tested

9060b rev 15772 contains some formatting changes.

#141 Updated by Constantin Asofiei over 1 year ago

Also, please rebase.

#142 Updated by Artur Școlnic over 1 year ago

Rebased 9060b on trunk rev 15849.

#143 Updated by Artur Școlnic over 1 year ago

In order to make the Stream.readField work with the constant types, I had to change the method implementation so that the result is no longer assigned to the argument variable, but a new BDT is created and returned, this solves some of the issued but creates other ones, for example:

UnnamedStreams.safeInput().readField(var);

After the changes, the var will not hold the read value, in order to solve this issue, conversion changes need to be made so that code looks like:
var = UnnamedStreams.safeInput().readField(var);

Alternatively, the changes I made to the readField can be reverted and another solution implemented, though I am not sure if one exists, if a constant is passed to it, we cannot change it's value.

#144 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

In order to make the Stream.readField work with the constant types, I had to change the method implementation so that the result is no longer assigned to the argument variable, but a new BDT is created and returned, this solves some of the issued but creates other ones, for example:
[...]
After the changes, the var will not hold the read value, in order to solve this issue, conversion changes need to be made so that code looks like:
[...]

This is not correct - you can't re-assign the BDT var created by the application, to another instance.

Alternatively, the changes I made to the readField can be reverted and another solution implemented, though I am not sure if one exists, if a constant is passed to it, we cannot change it's value.

Where can I find the changes to readField?

#145 Updated by Artur Școlnic over 1 year ago

The changes are in 9060b/15859.

#146 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

The changes are in 9060b/15859.

Change the code in readField(BaseDataType) so that a new instance is created only and only if the parameter is a BaseDataTypeConstant. This should work.

#147 Updated by Artur Școlnic over 1 year ago

Sounds good.

#148 Updated by Constantin Asofiei over 1 year ago

Question: please remind me when readField(BaseDataType[] var) is emitted.

#149 Updated by Artur Școlnic over 1 year ago

Import statements, but I also saw code like:

import ^ newpath ^.

which is emitted as:
UnnamedStreams.safeInput().skipField();
UnnamedStreams.safeInput().readField(var);
UnnamedStreams.safeInput().skipField();
UnnamedStreams.safeInput().resetCurrentLine();

In this particular context, I am not sure what the ^^ are.

#150 Updated by Constantin Asofiei over 1 year ago

Artur Școlnic wrote:

Import statements, but I also saw code like:

I meant the array overload of readField

#151 Updated by Artur Școlnic over 1 year ago

I can't see an obvious usage in the customer project i am working with.

#152 Updated by Artur Școlnic over 1 year ago

I addressed the review and fixed a few bugs, I no longer have failing tests in the unit test modules I am using (> 5k tests).
When it comes to the DYNAMIC_CACHE in decimal, I removed the synchronization due to it's overhead, the retrieved value is checked anyway so there is no risk of retrieving a wrong one. I don't remember if I did extensive testing for this, but I could do it if needed.
I committed the changes to 9060b/15860.

#153 Updated by Constantin Asofiei about 1 year ago

Review of 9060b rev 15860:
  • in Stream.readField, return res; should be used for non-constant case (to keep the existing behavior)
  • for readField(BDT[]) - please try some examples with extent fields.
  • TargetLob issue was not addressed (this.lob = new clob(field.get()))

#154 Updated by Artur Școlnic about 1 year ago

Constantin Asofiei wrote:

  • in Stream.readField, return res; should be used for non-constant case (to keep the existing behavior)

The existing behavior of readField is to reassign the value, it does not return anything. There is no need to return var.duplicate() for a non constant var.

  • TargetLob issue was not addressed (this.lob = new clob(field.get()))

this.lob will have a mutable type constructed from a potentially immutable field reference.

Can I move on to more extensive testing?

#155 Updated by Constantin Asofiei about 1 year ago

Artur Școlnic wrote:

Constantin Asofiei wrote:

  • in Stream.readField, return res; should be used for non-constant case (to keep the existing behavior)

The existing behavior of readField is to reassign the value, it does not return anything. There is no need to return var.duplicate() for a non constant var.

In readField(FieldReference) you have var = readField(var);. You are right that original code was not returning anything, but lets keep consistent and return var; even if is the same instance as the argument.

  • TargetLob issue was not addressed (this.lob = new clob(field.get()))

this.lob will have a mutable type constructed from a potentially immutable field reference.

I don't understand. Original was:

   public TargetLob(FieldReference field)
   {
      this.field = field;
      this.lob = (LargeObject) field.get();
   }

Now is:
   public TargetLob(FieldReference field)
   {
      this.field = field;
      this.lob = new clob(field.get());
   }

LargeObject instances which can be at a field are blob, clob, longchar and memptr. You are now limiting this constructor only for clob types.

Can I move on to more extensive testing?

Yes, after addressing above and rebase.

#156 Updated by Constantin Asofiei about 1 year ago

Does this.lob = (LargeObject) field.get().duplicate(); work, only and only if field.get() is a BaseDataTypeConstant?

#157 Updated by Artur Școlnic about 1 year ago

Constantin Asofiei wrote:

Does this.lob = (LargeObject) field.get().duplicate(); work, only and only if field.get() is a BaseDataTypeConstant?

Should work, duplicate returns a mutable instance.

#158 Updated by Artur Școlnic about 1 year ago

  • Status changed from Review to Internal Test

#159 Updated by Constantin Asofiei about 1 year ago

In TargetLob(FieldReference field), field.get() is called twice per call. Please use bdt for the second call.

#160 Updated by Constantin Asofiei about 1 year ago

Also, testing needs to include performance testing, where possible.

#161 Updated by Artur Școlnic about 1 year ago

In fuzzyMethodLookup, the line:

System.out.printf("\n   CALLER %s\n   PARAMETERS: %d\n", node.dumpTree(true), caller.length);

the node argument is null and is never assigned, so it causes a NPE, the changes in 9060b opened new execution paths, previously this code was never reached.
The method is called in FuzzyMethodRt.lookup
fuzzyMethodLookup(methodName, sig, accessMode, isStatic, internal, null);

Should I add a conditional to the print or delete it?

#162 Updated by Constantin Asofiei about 1 year ago

I don't understand, fuzzyMethodLookup is almost sure called. I see WARNING: more than one method def found using fuzzy looku in lots of projects.

Why do you get the NPE?

#163 Updated by Artur Școlnic about 1 year ago

The call is fuzzyMethodLookup(methodName, sig, accessMode, isStatic, internal, null);
null is passed for the node parameter, and it is not reassigned, it simply could have not worked.
Previously, in fuzzyMethodLookup the method was returning earlier and was not reaching this point.

#164 Updated by Constantin Asofiei about 1 year ago

Artur Școlnic wrote:

The call is fuzzyMethodLookup(methodName, sig, accessMode, isStatic, internal, null);

Please post the stacktrace for this call.

#165 Updated by Artur Școlnic about 1 year ago

Caused by: java.lang.NullPointerException
│  │  │  │  │                   at com.goldencode.p2j.uast.FuzzyMethodLookup.fuzzyMethodLookup(FuzzyMethodLookup.java:638)
│  │  │  │  │                   at com.goldencode.p2j.util.FuzzyMethodRt.lookup(FuzzyMethodRt.java:139)
│  │  │  │  │                   at com.goldencode.p2j.util.ControlFlowOps.resolveLegacyEntry(ControlFlowOps.java:6190)
│  │  │  │  │                   at com.goldencode.p2j.util.ControlFlowOps.resolveLegacyEntry(ControlFlowOps.java:5542)
│  │  │  │  │                   at com.goldencode.p2j.util.ControlFlowOps.resolveLegacyEntry(ControlFlowOps.java:4475)
│  │  │  │  │                   at com.goldencode.p2j.util.ObjectOps.invokePolyWorker(ObjectOps.java:860)
│  │  │  │  │                   at com.goldencode.p2j.util.ObjectOps.invokeStandalonePoly(ObjectOps.java:699)

#166 Updated by Constantin Asofiei about 1 year ago

Artur, on a second thought: this is a runtime stack - that location should never be reached. Something is wrong in 9060b.

#167 Updated by Artur Școlnic about 1 year ago

 else if (list.isEmpty())
      {
         if (debug)
         {
            // if we still have ambiguous matches, display any stored error
            if (error[0] != null)
            {
               System.out.println(error[0]);
            }

            System.out.println("\n\n----------------fuzzyMethodLookup NO MATCH SUCKA!----------------\n");
         }

         return null; // ---> trunk
      }

      System.out.println("WARNING: more than one method def found using fuzzy lookup: " + name + 
                         " from class " + getClassName());

      System.out.printf("\n   CALLER %s\n   PARAMETERS: %d\n", node.dumpTree(true), caller.length); // ---> 9060b

the MatchMetrics list is no longer empty.

#168 Updated by Artur Școlnic about 1 year ago

With a simple null check, the failing tests are passing.

#169 Updated by Constantin Asofiei about 1 year ago

Artur, see FuzzyMethodRt.resolveSignature - type = arg.getClass().getSimpleName(); - if arg is a BaseDataTypeConstant, resolve the super-class. We already do this in ControlFlowOps.calculateType. But please review resolveLegacyEntry if there is any other missing place where BaseDataTypeConstant is not honored.

#170 Updated by Artur Școlnic about 1 year ago

Constantin Asofiei wrote:

Artur, see FuzzyMethodRt.resolveSignature - type = arg.getClass().getSimpleName(); - if arg is a BaseDataTypeConstant, resolve the super-class. We already do this in ControlFlowOps.calculateType. But please review resolveLegacyEntry if there is any other missing place where BaseDataTypeConstant is not honored.

It did not solve the issue. It still looks wrong to me to pass a null value for node then calling dumpTree on it.

#171 Updated by Constantin Asofiei about 1 year ago

The point is: the type at that place most likely is a i.e. integerConstant - look further or otherwise debug and find why a constant type is being used in FuzzyMethodRt. This must not happen.

#172 Updated by Artur Școlnic about 1 year ago

  ObjectOps.invokeStandalonePoly(Assert.class, "AreEqual", "III", new Object[]{actual.unwrapBuffer().bufferField(i).unwrapBufferField().value(), expect.unwrapBuffer().bufferField(fieldName).unwrapBufferField().value(), msg});

the arguments are integerConstant, but i don't think this is an issue, there are a lot of calls in FuzzyMethodRt with constant arguments that do not cause any issues. I reverted the changes made in FuzzyMethodLookup and the tests pass.

#173 Updated by Constantin Asofiei about 1 year ago

Artur, I understand that, but constant types should be resolved to their actual types. There is a bug somewhere in FuzzyMethodRt or resolveLegacyEntry. If we target can exactly on the type, a constant argument should not interfere with this.

Also, System.out.println("WARNING: more than one method .. you mention in FuzzyMethodRt is a debug output which until now was emitted only for conversion; for runtime, we need to include it in if (debug) processing (but conversion should still emit it, as its useful). In any case, please try first to find the 'root cause' why constant types are not resolved to their BDT super-class.

#174 Updated by Artur Școlnic about 1 year ago

I committed the fix for this and other issues to 9060b/15882. Constantin, maybe you can take a look while I continue testing.

#175 Updated by Constantin Asofiei about 1 year ago

Artur, the change in ControlFlowOps and TargetLob are good. But, the RecordBuffer change is not OK:
  • you change the datum in setCompareMode, but the argument at the caller remains unchanged (thus the case-sensitivity is unchanged)
  • also, I think is part of a larger problem: we have DataHandler.getField(), for which the returned instance can be assumed mutable in lots of places. I think we need to change getField to return a mutable instance, and not a constant, in all implementations.

#176 Updated by Constantin Asofiei about 1 year ago

Constantin Asofiei wrote:

  • also, I think is part of a larger problem: we have DataHandler.getField(), for which the returned instance can be assumed mutable in lots of places. I think we need to change getField to return a mutable instance, and not a constant, in all implementations.

Or otherwise add a parameter to getField(boolean allowConstant), so that a mutable instance is created only in places where is needed; this can work only if these places can be identified.

#177 Updated by Artur Școlnic about 1 year ago

Returning a mutable instance for all implementations seems to work fine, the testing passed.

#178 Updated by Constantin Asofiei about 1 year ago

OK, lets leave it as this for now.

For 9060b:
  • please do bzr diff -r 15867 --using meld and check the copyright year for all files
  • run javadoc and fix problems only in the files changed in this branch

Otherwise, lets continue testing - all apps need to be nested, performance included.

#179 Updated by Alexandru Lungu about 1 year ago

  • Related to Bug #10004: NativeInvoker fails if an argument is of type integerConstant added

#180 Updated by Artur Școlnic about 1 year ago

Rebased 9060b on trunk rev 16051.

#181 Updated by Artur Școlnic 4 months ago

I extracted the more impactful and safe bdt caching code and tested the performance impact on a customer application. Lookup style requests were used for the experiment and I registered an average of ~5% performance regression with all apis affected in a negative way.

Also available in: Atom PDF