org.apfloat.ApfloatContext Maven / Gradle / Ivy
Show all versions of apfloat Show documentation
package org.apfloat;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apfloat.spi.BuilderFactory;
import org.apfloat.spi.FilenameGenerator;
import org.apfloat.spi.Util;
/**
* This class encapsulates the information needed by the apfloat implementation
* to perform computations.
*
* All environment related settings of an apfloat implementation are accessed
* through an ApfloatContext
. Such settings include for example
* the implementation provider class, maximum memory size to be used, and
* the file names that are used for temporary files.
*
* For performance reasons, access to an ApfloatContext
is not
* synchronized. Presumably, this won't be a problem in most cases. But, if
* the code needs to concurrently modify and access an ApfloatContext, all
* access to it should be externally synchronized.
*
* At simplest, there is just one ApfloatContext
, the global
* apfloat context. All settings in your application are retrieved through
* it. The global context is created when the ApfloatContext
* class is loaded, and it's thus always available.
*
* Values for the different settings in the global apfloat context are specified
* in the apfloat.properties
file, found in the class path. Since
* they are loaded via a ResourceBundle
named "apfloat", you can
* alternatively deploy a ResourceBundle
class named "apfloat" in
* your class path to avoid having a .properties file, or to define properties
* dynamically at run time.
*
* The different settings that can be specified in the apfloat.properties
* file are as follows:
*
*
* builderFactory
, name of the class set as in {@link #setBuilderFactory(BuilderFactory)}
* defaultRadix
, set as in {@link #setDefaultRadix(int)}
* maxMemoryBlockSize
, set as in {@link #setMaxMemoryBlockSize(long)}
* cacheL1Size
, set as in {@link #setCacheL1Size(int)}
* cacheL2Size
, set as in {@link #setCacheL2Size(int)}
* cacheBurst
, set as in {@link #setCacheBurst(int)}
* memoryThreshold
, set as in {@link #setMemoryThreshold(long)}
* shredMemoryTreshold
, set as in {@link #setSharedMemoryTreshold(long)}
* blockSize
, set as in {@link #setBlockSize(int)}
* numberOfProcessors
, set as in {@link #setNumberOfProcessors(int)}
* filePath
, set as in {@link #setProperty(String,String)} with property name {@link #FILE_PATH}
* fileInitialValue
, set as in {@link #setProperty(String,String)} with property name {@link #FILE_INITIAL_VALUE}
* fileSuffix
, set as in {@link #setProperty(String,String)} with property name {@link #FILE_SUFFIX}
* cleanupAtExit
, set as in {@link #setCleanupAtExit(boolean)}
*
*
* An example apfloat.properties
file could contain the following:
*
*
* builderFactory=org.apfloat.internal.IntBuilderFactory
* defaultRadix=10
* maxMemoryBlockSize=50331648
* cacheL1Size=8192
* cacheL2Size=262144
* cacheBurst=32
* memoryThreshold=65536
* sharedMemoryTreshold=65536
* blockSize=65536
* numberOfProcessors=1
* filePath=
* fileInitialValue=0
* fileSuffix=.ap
* cleanupAtExit=true
*
*
* The total memory size and the number of processors are detected automatically,
* as reported by the Java runtime, if they are not specified in the configuration
* bundle.
*
* If you need to create a complex multithreaded application that performs
* apfloat calculations in parallel using multiple threads, you may need to
* change the ApfloatContext settings for the different working threads.
*
* If thread-specific apfloat contexts are not specified, all threads will use
* the global context. To set a thread specific context, you would typically
* create a {@link #clone()} of the global (or other parent) context, and then
* set that context to the thread using {@link #setThreadContext(ApfloatContext,Thread)}.
* Note that if you do not create a clone of the context, the same context will still
* be used, since it's passed by reference.
*
* Typically you may need to set the following properties for each thread:
*
* - {@link #setNumberOfProcessors(int)}: Since the number of physical
* processors available is fixed, you may want to limit the amount
* of processors each thread can use. In many cases you will want each
* thread to use exactly one processor, and create as many threads as
* there are processors.
* - {@link #setMaxMemoryBlockSize(long)}: The physical memory is global
* and its amount is fixed as well. Since all threads share the global
* memory, you may want to limit the maximum amount of memory each thread
* can use. If you do this, you will probably just split the amount of
* memory between the threads, e.g. by dividing it equally. In this case
* you should set each thread to have a separate shared memory lock with
* {@link #setSharedMemoryLock(Object)}. In this solution all threads can
* allocate their maximum allowed memory block at the same time, and still
* the VM won't run out of memory.
* Another possibility is to set the whole global memory size as the maximum
* available for each thread, and use the same shared memory lock for every
* thread. This is actually the default behavior, if you don't call
* {@link #setMaxMemoryBlockSize(long)} nor {@link #setSharedMemoryLock(Object)}.
* This way all threads can access the maximum amount of physical memory
* available. The drawback is that the threads will synchronize on the
* same memory block, so only one thread can use it at a time. This can
* have a major effect on performance, if threads are idle, waiting to acquire
* the shared memory lock for most of the time. To work around this, some
* mechanism can be set up for pooling the threads competing for the same
* lock, and executing the task using parallel threads from the thread pool.
* For example the default apfloat multiplication algorithm uses such a
* mechanism. Note that synchronization against the shared memory lock
* will be used for all data blocks larger than the shared memory
* treshold (see {@link #getSharedMemoryTreshold()}).
* - {@link #setFilenameGenerator(FilenameGenerator)}: When you clone an
* ApfloatContext, the filename generator is by default shared. For most
* situations this is fine. If you for some reason want to separate
* the files generated in each thread, you can just set a new
* FilenameGenerator for each thread. In this case it's essential to
* configure the FilenameGenerators not to generate conflicting file
* names. You can do this easily by specifying a different directory or
* file suffix for each filename generator, or by specifying a different
* starting value for the file names (e.g. 1000000, 2000000, 3000000, ...).
* Setting the filename generator may also be relevant, if you use a
* distributed computing platform where separate physical machines (or
* at least separate VM processes) create temporary files in the same
* shared disk storage. In this case it's also essential to configure the
* different processes so that they do not generate conflicting file names.
*
*
* The other settings are generally global and do not typically need to
* be set differently for each thread.
*
* Unfortunately, Java doesn't allow detecting automatically many of the
* settings, such as cache sizes. Also, for optimal performance, it would
* usually be desirable to set each thread's processor affinity (which
* physical processor runs which thread), which is also not possible.
* If these features are added to the Java platform in the future, they
* may be added to the ApfloatContext
API as well.
*
* @version 1.8.0
* @author Mikko Tommila
*/
public class ApfloatContext
implements Cloneable
{
/**
* Property name for specifying the apfloat builder factory class.
*/
public static final String BUILDER_FACTORY = "builderFactory";
/**
* Property name for specifying the default radix.
*/
public static final String DEFAULT_RADIX = "defaultRadix";
/**
* Property name for specifying the maximum memory block size.
*/
public static final String MAX_MEMORY_BLOCK_SIZE = "maxMemoryBlockSize";
/**
* Property name for specifying the level 1 cache size.
*/
public static final String CACHE_L1_SIZE = "cacheL1Size";
/**
* Property name for specifying the level 2 cache size.
*/
public static final String CACHE_L2_SIZE = "cacheL2Size";
/**
* Property name for specifying the level 1 cache burst size.
*/
public static final String CACHE_BURST = "cacheBurst";
/**
* Property name for specifying the apfloat memory threshold.
*/
public static final String MEMORY_THRESHOLD = "memoryThreshold";
/**
* Property name for specifying the apfloat memory threshold.
*
* @deprecated Use {@link #MEMORY_THRESHOLD}.
*/
@Deprecated
public static final String MEMORY_TRESHOLD = "memoryTreshold";
/**
* Property name for specifying the apfloat shared memory threshold.
*/
public static final String SHARED_MEMORY_TRESHOLD = "sharedMemoryTreshold";
/**
* Property name for specifying the I/O block size.
*/
public static final String BLOCK_SIZE = "blockSize";
/**
* Property name for specifying the number of processors available.
*/
public static final String NUMBER_OF_PROCESSORS = "numberOfProcessors";
/**
* Property name for specifying the temporary file path.
*/
public static final String FILE_PATH = "filePath";
/**
* Property name for specifying the temporary file initial value.
*/
public static final String FILE_INITIAL_VALUE = "fileInitialValue";
/**
* Property name for specifying the temporary file suffix.
*/
public static final String FILE_SUFFIX = "fileSuffix";
/**
* Property name for specifying if clean-up should be done at program exit.
*/
public static final String CLEANUP_AT_EXIT = "cleanupAtExit";
// At system exit, run garbage collection and finalization to clean up temporary files
private static class CleanupThread
extends Thread
{
public CleanupThread()
{
super("apfloat shutdown clean-up thread");
}
public void run()
{
ApfloatMath.cleanUp(); // Clear references to static cached apfloats
System.gc();
System.gc();
System.runFinalization();
this.builderFactory.shutdown();
}
public void setBuilderFactory(BuilderFactory builderFactory)
{
this.builderFactory = builderFactory;
}
private BuilderFactory builderFactory;
}
/**
* Create a new ApfloatContext using the specified properties.
*
* @param properties The properties for the ApfloatContext.
*
* @exception org.apfloat.ApfloatConfigurationException If a property value can't be converted to the correct type.
*/
public ApfloatContext(Properties properties)
throws ApfloatConfigurationException
{
this.properties = (Properties) ApfloatContext.defaultProperties.clone();
this.properties.putAll(properties);
setProperties(this.properties);
}
/**
* Get the ApfloatContext for the calling thread. If a thread-specific
* context has not been specified, the global context is returned.
*
* @return The ApfloatContext for the calling thread.
*/
public static ApfloatContext getContext()
{
ApfloatContext ctx = getThreadContext();
if (ctx == null)
{
ctx = getGlobalContext();
}
return ctx;
}
/**
* Get the global ApfloatContext.
*
* @return The global ApfloatContext.
*/
public static ApfloatContext getGlobalContext()
{
return ApfloatContext.globalContext;
}
/**
* Get the thread-specific ApfloatContext for the calling thread.
*
* @return The ApfloatContext for the calling thread, or null
if one has not been specified.
*/
public static ApfloatContext getThreadContext()
{
return getThreadContext(Thread.currentThread());
}
/**
* Get the thread-specific ApfloatContext for the specified thread.
*
* @param thread The thread whose ApfloatContext is to be returned.
*
* @return The ApfloatContext for the specified thread, or null
if one has not been specified.
*/
public static ApfloatContext getThreadContext(Thread thread)
{
return ApfloatContext.threadContexts.get(thread);
}
/**
* Set the thread-specific ApfloatContext for the calling thread.
*
* @param threadContext The ApfloatContext for the calling thread.
*/
public static void setThreadContext(ApfloatContext threadContext)
{
setThreadContext(threadContext, Thread.currentThread());
}
/**
* Set the thread-specific ApfloatContext for the specified thread.
*
* @param threadContext The ApfloatContext for the specified thread.
* @param thread The thread whose ApfloatContext is to be set.
*/
public static void setThreadContext(ApfloatContext threadContext, Thread thread)
{
ApfloatContext.threadContexts.put(thread, threadContext);
}
/**
* Removes the thread-specific context for the current thread.
*/
public static void removeThreadContext()
{
removeThreadContext(Thread.currentThread());
}
/**
* Removes the thread-specific context for the specified thread.
*
* @param thread The thread whose ApfloatContext is to be removed.
*/
public static void removeThreadContext(Thread thread)
{
ApfloatContext.threadContexts.remove(thread);
}
/**
* Removes all thread-specific ApfloatContexts.
*/
public static void clearThreadContexts()
{
ApfloatContext.threadContexts.clear();
}
/**
* Get the BuilderFactory.
*
* @return The BuilderFactory for this ApfloatContext.
*/
public BuilderFactory getBuilderFactory()
{
return this.builderFactory;
}
/**
* Set the BuilderFactory.
*
* @param builderFactory The BuilderFactory for this ApfloatContext.
*/
public void setBuilderFactory(BuilderFactory builderFactory)
{
this.properties.setProperty(BUILDER_FACTORY, builderFactory.getClass().getName());
this.builderFactory = builderFactory;
if (this.cleanupThread != null)
{
this.cleanupThread.setBuilderFactory(builderFactory);
}
}
/**
* Get the FilenameGenerator.
*
* @return The FilenameGenerator for this ApfloatContext.
*/
public FilenameGenerator getFilenameGenerator()
{
return this.filenameGenerator;
}
/**
* Set the FilenameGenerator.
*
* @param filenameGenerator The FilenameGenerator for this ApfloatContext.
*/
public void setFilenameGenerator(FilenameGenerator filenameGenerator)
{
this.properties.setProperty(FILE_PATH, filenameGenerator.getPath());
this.properties.setProperty(FILE_INITIAL_VALUE, String.valueOf(filenameGenerator.getInitialValue()));
this.properties.setProperty(FILE_SUFFIX, filenameGenerator.getSuffix());
this.filenameGenerator = filenameGenerator;
}
/**
* Get the default radix.
*
* @return The default radix for this ApfloatContext.
*/
public int getDefaultRadix()
{
return this.defaultRadix;
}
/**
* Set the default radix.
* The default value is 10.
*
* @param radix The default radix for this ApfloatContext.
*/
public void setDefaultRadix(int radix)
{
radix = Math.min(Math.max(radix, Character.MIN_RADIX), Character.MAX_RADIX);
this.properties.setProperty(DEFAULT_RADIX, String.valueOf(radix));
this.defaultRadix = radix;
}
/**
* Get the maximum memory block size.
*
* @return The maximum memory block size.
*
* @see #setMaxMemoryBlockSize(long)
*/
public long getMaxMemoryBlockSize()
{
return this.maxMemoryBlockSize;
}
/**
* Set the maximum allowed memory block size in bytes.
* Apfloat will allocate an array at most of this size
* for calculations using this context.
* The minimum value for this setting is 65536.
*
* If you set the value of this parameter too low,
* performance will suffer greatly as data is unnecessarily
* paged to disk. If you set this value too high, your
* application can crash with an OutOfMemoryError
.
*
* The default value for this setting is 80% of the total memory
* available to the VM at application startup, as reported by
* Runtime.totalMemory()
, rounded down to the nearest
* power of two or three times a power of two.
*
* @param maxMemoryBlockSize Maximum allocated memory block size in bytes.
*/
public void setMaxMemoryBlockSize(long maxMemoryBlockSize)
{
// Note that setting the 64-bit long is not guaranteed to be atomic
// However on 32-bit JVMs the upper word is always zero, and on 64-bit JVMs the update is probably atomic
maxMemoryBlockSize = Util.round23down(Math.max(maxMemoryBlockSize, 65536));
this.properties.setProperty(MAX_MEMORY_BLOCK_SIZE, String.valueOf(maxMemoryBlockSize));
this.maxMemoryBlockSize = maxMemoryBlockSize;
}
/**
* Get the level 1 cache size.
*
* @return The level 1 cache size.
*
* @see #setCacheL1Size(int)
*/
public int getCacheL1Size()
{
return this.cacheL1Size;
}
/**
* Set the L1 cache size in bytes. The minimum value for this setting is 512.
*
* This setting has a minor performance impact on some memory
* intensive operations. Unless you really want to tweak the performance,
* it's better to not touch this setting.
*
* The default value for this setting is 8kB.
*
* @param cacheL1Size The level 1 cache size in bytes.
*/
public void setCacheL1Size(int cacheL1Size)
{
cacheL1Size = Util.round2down(Math.max(cacheL1Size, 512));
this.properties.setProperty(CACHE_L1_SIZE, String.valueOf(cacheL1Size));
this.cacheL1Size = cacheL1Size;
}
/**
* Get the level 2 cache size.
*
* @return The level 2 cache size.
*
* @see #setCacheL2Size(int)
*/
public int getCacheL2Size()
{
return this.cacheL2Size;
}
/**
* Set the L2 cache size in bytes. The minimum value for this setting is 2048.
*
* This setting has a minor performance impact on some memory
* intensive operations. Unless you really want to tweak the performance,
* it's better to not touch this setting.
*
* The default value for this setting is 256kB.
*
* @param cacheL2Size The level 2 cache size in bytes.
*/
public void setCacheL2Size(int cacheL2Size)
{
cacheL2Size = Util.round2down(Math.max(cacheL2Size, 2048));
this.properties.setProperty(CACHE_L2_SIZE, String.valueOf(cacheL2Size));
this.cacheL2Size = cacheL2Size;
}
/**
* Get the level 1 cache burst size.
*
* @return The cache burst size.
*
* @see #setCacheBurst(int)
*/
public int getCacheBurst()
{
return this.cacheBurst;
}
/**
* Set the L1 cache burst block size in bytes.
* This value is also known as "L1 cache line size".
*
* Some common values are:
*
* - 16 for 486 processors
* - 32 for Pentium MMX/II/III/Celeron series and Itanium processors
* - 64 for Pentium 4 and Itanium 2 processors
*
* The processor will move at least this amount of bytes
* whenever data is moved between the level 1 cache and
* other memory (lower level cache or main memory).
* Note that other cache levels than L1 may have a different
* line size. The minimum value for this setting is 8.
*
* This setting has a minor performance impact on some memory
* intensive operations. Unless you really want to tweak the performance,
* it's usually better to not touch this setting. Though, if you have e.g.
* a Pentium 4 processor, you may want to increase the value
* of this setting to 64 from the default value of 32.
*
* @param cacheBurst The number of bytes in a L1 cache line.
*/
public void setCacheBurst(int cacheBurst)
{
cacheBurst = Util.round2down(Math.max(cacheBurst, 8));
this.properties.setProperty(CACHE_BURST, String.valueOf(cacheBurst));
this.cacheBurst = cacheBurst;
}
/**
* Get the memory threshold.
*
* If the value is larger than the maximum value that can be presented
* in an integer, then Integer.MAX_VALUE
is returned.
*
* @return The memory threshold.
*
* @deprecated Use {@link #getMemoryThreshold()}.
*/
@Deprecated
public int getMemoryTreshold()
{
return (int) Math.min(Integer.MAX_VALUE, getMemoryThreshold());
}
/**
* Set the maximum size of apfloats in bytes that are
* stored in memory within this context.
*
* @param memoryThreshold The number of bytes that apfloats that are stored in memory will at most have within this context.
*
* @deprecated Use {@link #setMemoryThreshold(long)}.
*/
@Deprecated
public void setMemoryTreshold(int memoryThreshold)
{
setMemoryThreshold(memoryThreshold);
}
/**
* Get the memory threshold.
*
* @return The memory threshold.
*/
public long getMemoryThreshold()
{
return this.memoryThreshold;
}
/**
* Set the maximum size of apfloats in bytes that are
* stored in memory within this context. The minimum value for this setting is 128.
*
* If the memory threshold is too small, performance will suffer as
* small numbers are stored to disk, and the amount of disk I/O
* overhead becomes significant. On the other hand, if the memory
* treshold is too big, you can get an OutOfMemoryError
.
*
* The optimal value depends greatly on each application. Obviously,
* if you have plenty of heap space and don't create too many too big
* numbers you are not likely to have problems. The default value of
* this setting is 64kB, or the maximum heap size divided by 1024,
* whichever is larger.
*
* @param memoryThreshold The number of bytes that apfloats that are stored in memory will at most have within this context.
*/
public void setMemoryThreshold(long memoryThreshold)
{
memoryThreshold = Math.max(memoryThreshold, 128);
this.properties.setProperty(MEMORY_TRESHOLD, String.valueOf(memoryThreshold));
this.properties.setProperty(MEMORY_THRESHOLD, String.valueOf(memoryThreshold));
this.memoryThreshold = memoryThreshold;
}
/**
* Get the shared memory treshold.
*
* @return The shared memory treshold.
*
* @see #setSharedMemoryTreshold(long)
*/
public long getSharedMemoryTreshold()
{
return this.sharedMemoryTreshold;
}
/**
* Set the maximum size of apfloats in bytes that can be used
* without synchronizing against the shared memory lock.
* The minimum value for this setting is 128.
*
* If only one thread is used then this setting has no effect.
* If multiple threads are used, and this setting is too small,
* performance will suffer as the synchronization blocking and
* other overhead becomes significant. On the other hand, if the
* numbers are being stored in memory, and the shared memory
* treshold is too big, you can get an OutOfMemoryError
.
*
* The optimal value depends on the application and the way parallelism
* is used. As a rule of thumb, this should be set to a value that is
* the maximum memory block size divided by the number of parallel threads.
* The default is somewhat more conservatively this number divided by 32.
*
* @param sharedMemoryTreshold The number of bytes that apfloats will at most have, without synchronizing against the shared memory lock, within this context.
*/
public void setSharedMemoryTreshold(long sharedMemoryTreshold)
{
sharedMemoryTreshold = Math.max(sharedMemoryTreshold, 128);
this.properties.setProperty(SHARED_MEMORY_TRESHOLD, String.valueOf(sharedMemoryTreshold));
this.sharedMemoryTreshold = sharedMemoryTreshold;
}
/**
* Get the I/O block size.
*
* @return The I/O block size.
*
* @see #setBlockSize(int)
*/
public int getBlockSize()
{
return this.blockSize;
}
/**
* Set the efficient I/O block size in bytes for
* this context. The minimum value for this setting is 128.
*
* If the block size is too small, the overhead of each I/O call will
* definetely have an adverse effect on performance. Setting the block
* size very big will not affect performance significantly, but can
* increase intermediate memory consumption a lot, possibly resulting
* in running out of memory with an OutOfMemoryError
. A
* recommended minimum value is at least a few kilobytes.
*
* In many places, data in files is accessed in reverse order, fetching
* blocks of this size. Probably the optimal value of this setting is
* roughly half of the read-ahead buffer size of you hard disk.
* The default value is 64kB.
*
* @param blockSize The I/O block size in bytes to be used in calculations using this context.
*/
public void setBlockSize(int blockSize)
{
blockSize = Util.round2down(Math.max(blockSize, 128));
this.properties.setProperty(BLOCK_SIZE, String.valueOf(blockSize));
this.blockSize = blockSize;
}
/**
* Get the number of processors that should be used for parallel calculations.
*
* @return The number of processors.
*
* @see #setNumberOfProcessors(int)
*/
public int getNumberOfProcessors()
{
return this.numberOfProcessors;
}
/**
* Set the number of processors available to parallel calculations using
* this context. The minimum value for this setting is 1.
* The default is to use all processors (CPU cores) available.
*
* Note that if you change the number of processors after the library has
* been initialized, the number of threads available to the ExecutorService
* is not changed. If you want to change that too, it can be done easily with
* setExecutorService(ApfloatContext.getDefaultExecutorService())
.
*
* @param numberOfProcessors Number of processors available to parallel calculations using this context.
*
* @see #getDefaultExecutorService
*
* @see #setExecutorService(ExecutorService)
*/
public void setNumberOfProcessors(int numberOfProcessors)
{
numberOfProcessors = Math.max(numberOfProcessors, 1);
this.properties.setProperty(NUMBER_OF_PROCESSORS, String.valueOf(numberOfProcessors));
this.numberOfProcessors = numberOfProcessors;
}
/**
* Get if clean-up should be performed at the time the program exits.
*
* @return true
if clean-up will be done at JVM exit, or false
if not.
*
* @see #setCleanupAtExit(boolean)
*/
public boolean getCleanupAtExit()
{
return (this.cleanupThread != null);
}
/**
* Set if clean-up should be performed at the time the program exits.
* The clean-up runs garbage collection and finalization to remove any
* remaining temporary files that may have been created.
* The default behavior is true
.
*
* For example unsigned applets must have this property set to false
,
* since they do not have access to setting shutdown hooks.
*
* Note that if this setting is ever set to true
in any
* ApfloatContext
(and never set to false
* subsequently in that context), then clean-up will be performed.
*
* Also note that having the shutdown hook set will prevent class garbage
* collection i.e. the apfloat classes can't be unloaded if the shutdown
* hook still references the ApfloatContext class. If class unloading is
* desired then the cleanupAtExit property should be set to false first.
*
* @param cleanupAtExit true
if clean-up should be done at JVM exit, or false
if not.
*/
public void setCleanupAtExit(boolean cleanupAtExit)
{
this.properties.setProperty(CLEANUP_AT_EXIT, String.valueOf(cleanupAtExit));
if (cleanupAtExit && this.cleanupThread == null)
{
this.cleanupThread = new CleanupThread();
this.cleanupThread.setBuilderFactory(this.builderFactory);
Runtime.getRuntime().addShutdownHook(this.cleanupThread);
}
else if (!cleanupAtExit && this.cleanupThread != null)
{
Runtime.getRuntime().removeShutdownHook(this.cleanupThread);
this.cleanupThread = null;
}
}
/**
* Get the value of a property as string.
* The name of the property can be any of the constants defined above.
*
* @param propertyName The name of the property.
*
* @return The value of the property as a String
.
*/
public String getProperty(String propertyName)
{
return this.properties.getProperty(propertyName);
}
/**
* Get the value of a property as string, with the provided default
* value if the property is not set.
*
* @param propertyName The name of the property.
* @param defaultValue The default value to be returned, if the property is not set.
*
* @return The value of the property as a String
.
*
* @since 1.7.0
*/
public String getProperty(String propertyName, String defaultValue)
{
return this.properties.getProperty(propertyName, defaultValue);
}
/**
* Set the value of a property as string.
* The name of the property can be any of the constants defined above.
*
* @param propertyName The name of the property.
* @param propertyValue The value of the property as a String
.
*
* @exception org.apfloat.ApfloatConfigurationException If the property value can't be converted to the correct type.
*/
public void setProperty(String propertyName, String propertyValue)
throws ApfloatConfigurationException
{
try
{
if (propertyName.equals(BUILDER_FACTORY))
{
setBuilderFactory((BuilderFactory) Class.forName(propertyValue).newInstance());
}
else if (propertyName.equals(DEFAULT_RADIX))
{
setDefaultRadix(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(MAX_MEMORY_BLOCK_SIZE))
{
setMaxMemoryBlockSize(Long.parseLong(propertyValue));
}
else if (propertyName.equals(CACHE_L1_SIZE))
{
setCacheL1Size(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(CACHE_L2_SIZE))
{
setCacheL2Size(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(CACHE_BURST))
{
setCacheBurst(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(MEMORY_TRESHOLD) || propertyName.equals(MEMORY_THRESHOLD))
{
setMemoryThreshold(Long.parseLong(propertyValue));
}
else if (propertyName.equals(SHARED_MEMORY_TRESHOLD))
{
setSharedMemoryTreshold(Long.parseLong(propertyValue));
}
else if (propertyName.equals(BLOCK_SIZE))
{
setBlockSize(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(NUMBER_OF_PROCESSORS))
{
setNumberOfProcessors(Integer.parseInt(propertyValue));
}
else if (propertyName.equals(FILE_PATH))
{
setFilenameGenerator(new FilenameGenerator(propertyValue,
getProperty(FILE_INITIAL_VALUE),
getProperty(FILE_SUFFIX)));
}
else if (propertyName.equals(FILE_INITIAL_VALUE))
{
setFilenameGenerator(new FilenameGenerator(getProperty(FILE_PATH),
propertyValue,
getProperty(FILE_SUFFIX)));
}
else if (propertyName.equals(FILE_SUFFIX))
{
setFilenameGenerator(new FilenameGenerator(getProperty(FILE_PATH),
getProperty(FILE_INITIAL_VALUE),
propertyValue));
}
else if (propertyName.equals(CLEANUP_AT_EXIT))
{
setCleanupAtExit(Boolean.parseBoolean(propertyValue));
}
else
{
this.properties.setProperty(propertyName, propertyValue);
}
}
catch (Exception e)
{
throw new ApfloatConfigurationException("Error setting property \"" + propertyName + "\" to value \"" + propertyValue + '\"', e);
}
}
/**
* Get the values of all properties as strings.
* The names of the properties are all of the constants defined above.
*
* @return The properties.
*/
public Properties getProperties()
{
return (Properties) this.properties.clone();
}
/**
* Get the shared memory lock object.
* All internal functions that allocate a memory block larger than the
* shared memory treshold should synchronize the allocation and memory access
* on the object returned by this method.
*
* @return The object on which large memory block allocation and access should be synchronized.
*/
public Object getSharedMemoryLock()
{
return this.sharedMemoryLock;
}
/**
* Set the shared memory lock object.
* All internal functions that allocate a memory block larger than the
* shared memory treshold should synchronize the allocation and memory access
* on the object passed to this method.
*
* The object is not used for anything else than synchronization, so the
* class of the object should really be java.lang.Object
. One
* would typically call this method e.g. as
* ctx.setSharedMemoryLock(new Object())
.
*
* @param lock The object on which large memory block allocation and access should be synchronized.
*/
public void setSharedMemoryLock(Object lock)
{
this.sharedMemoryLock = lock;
}
/**
* Get the ExecutorService.
* It can be used for executing operations in parallel.
*
* By default the ExecutorService is a thread pool that is
* shared by all the ApfloatContexts. The threads in the pool
* are daemon threads so the thread pool requires no clean-up
* at shutdown time.
*
* @return The ExecutorService.
*
* @see #getDefaultExecutorService
*
* @since 1.1
*/
public ExecutorService getExecutorService()
{
return this.executorService;
}
/**
* Set the ExecutorService.
*
* If a custom ExecutorService is used, e.g. a thread pool, then the number
* of available threads in the pool should match the number of processors
* set to all ApfloatContexts with {@link #setNumberOfProcessors(int)}.
*
* Note that if a custom ExecutorService that requires shutdown is used,
* it is the caller's responsibility to clean up the ExecutorService
* at shutdown.
*
* @param executorService The ExecutorService.
*
* @see #getDefaultExecutorService
*
* @since 1.1
*/
public void setExecutorService(ExecutorService executorService)
{
this.executorService = executorService;
}
/**
* Get an arbitrary object as an attribute for this ApfloatContext.
*
* @param name Name of the attribute.
*
* @return Value of the attribute or null
if the attribute doesn't exist.
*/
public Object getAttribute(String name)
{
return this.attributes.get(name);
}
/**
* Set an arbitrary object as an attribute for this ApfloatContext.
*
* @param name Name of the attribute.
* @param value Value of the attribute.
*
* @return Previous value of the attribute or null
if the attribute didn't exist.
*/
public Object setAttribute(String name, Object value)
{
return this.attributes.put(name, value);
}
/**
* Remove an attribute from this ApfloatContext.
*
* @param name Name of the attribute.
*
* @return Value of the attribute or null
if the attribute didn't exist.
*/
public Object removeAttribute(String name)
{
return this.attributes.remove(name);
}
/**
* Get names of all attributes for this ApfloatContext.
*
* @return Names of all attributes as strings.
*/
public Enumeration getAttributeNames()
{
return this.attributes.keys();
}
/**
* Loads properties from a properties file or resource bundle.
* First the ResourceBundle
by the name "apfloat" is
* located, then all properties found from that resource bundle
* are put to a Properties
object.
*
* The resource bundle is found basically using the following logic
* (note that this is standard Java ResourceBundle
* functionality), in this order whichever is found first:
*
*
* - From the class named
apfloat
(that should be a subclass of ResourceBundle
), in the current class path
* - From the file "apfloat.properties" in the current class path
*
*
* @return Properties found in the "apfloat" resource bundle, or an empty Properties
object, if the resource bundle is not found.
*/
public static Properties loadProperties()
throws ApfloatRuntimeException
{
Properties properties = new Properties();
try
{
ResourceBundle resourceBundle = ResourceBundle.getBundle("apfloat");
Enumeration keys = resourceBundle.getKeys();
while (keys.hasMoreElements())
{
String key = keys.nextElement();
properties.setProperty(key, resourceBundle.getString(key));
}
}
catch (MissingResourceException mre)
{
// Ignore - properties file or class is not found or can't be read
}
return properties;
}
/**
* Returns a new instance of a default ExecutorService.
*
* The default executor service is a thread-limited pool
* where the number of threads is one less than the number
* of processors set with {@link #setNumberOfProcessors(int)}.
*
* @return A new instance of a default ExecutorService.
*
* @since 1.3
*/
public static ExecutorService getDefaultExecutorService()
{
// Executor service with all daemon threads, to avoid clean-up
ThreadFactory threadFactory = new ThreadFactory()
{
public Thread newThread(Runnable runnable)
{
Thread thread = this.defaultThreadFactory.newThread(runnable);
thread.setDaemon(true);
return thread;
}
private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
};
int numberOfThreads = Math.max(1, getContext().getNumberOfProcessors() - 1);
ThreadPoolExecutor executorService = new ThreadPoolExecutor(numberOfThreads, numberOfThreads, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(), threadFactory);
executorService.allowCoreThreadTimeOut(true);
return executorService;
}
/**
* Set the values of all properties as strings.
* The names of the properties can be all of the constants defined above.
*
* @param properties The properties.
*
* @exception org.apfloat.ApfloatConfigurationException If a property value can't be converted to the correct type.
*/
public void setProperties(Properties properties)
throws ApfloatConfigurationException
{
Enumeration> keys = properties.propertyNames();
while (keys.hasMoreElements())
{
String key = (String) keys.nextElement();
setProperty(key, properties.getProperty(key));
}
}
/**
* Creates a copy of this object.
*
* The clone has the same BuilderFactory and FilenameGenerator members
* and the same shared memory lock and ExecutorService as the original
* ApfloatContext.
*
* A shallow copy of the property set and the attribute set is created.
* Thus setting a property or attribute on the clone will not set it
* in the original object. Since the actual attributes (values) are shared,
* if an attribute is mutable and is modified in the clone, the modified
* value will appear in the original also.
*
* @return A mostly shallow copy of this object.
*/
public Object clone()
{
try
{
ApfloatContext ctx = (ApfloatContext) super.clone(); // Copy all attributes by reference
ctx.properties = (Properties) ctx.properties.clone(); // Create shallow copies
ctx.attributes = new ConcurrentHashMap(ctx.attributes);
return ctx;
}
catch (CloneNotSupportedException cnse)
{
// Should not occur
throw new InternalError();
}
}
private static ApfloatContext globalContext;
private static Map threadContexts = new ConcurrentWeakHashMap(); // Use a weak hash map to automatically remove completed threads; concurrent to avoid blocking threads
private static Properties defaultProperties;
private static ExecutorService defaultExecutorService;
private volatile BuilderFactory builderFactory;
private volatile FilenameGenerator filenameGenerator;
private volatile int defaultRadix;
private volatile long maxMemoryBlockSize;
private volatile int cacheL1Size;
private volatile int cacheL2Size;
private volatile int cacheBurst;
private volatile long memoryThreshold;
private volatile long sharedMemoryTreshold;
private volatile int blockSize;
private volatile int numberOfProcessors;
private volatile CleanupThread cleanupThread;
private volatile Properties properties;
private volatile Object sharedMemoryLock = new Object();
private volatile ExecutorService executorService = ApfloatContext.defaultExecutorService;
private volatile ConcurrentHashMap attributes = new ConcurrentHashMap();
static
{
ApfloatContext.defaultProperties = new Properties();
// Try to use up to 80% of total memory and all processors
long totalMemory;
try
{
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage memoryUsage = memoryBean.getHeapMemoryUsage();
totalMemory = Math.max(memoryUsage.getCommitted(), memoryUsage.getMax());
}
catch (NoClassDefFoundError ncdfe)
{
// The ManagementFactory class might be unavailable
totalMemory = Runtime.getRuntime().maxMemory();
}
long maxMemoryBlockSize = Util.round23down(totalMemory / 5 * 4);
int numberOfProcessors = Runtime.getRuntime().availableProcessors();
long memoryThreshold = Math.max(maxMemoryBlockSize >> 10, 65536);
int blockSize = Util.round2down((int) Math.min(memoryThreshold, Integer.MAX_VALUE));
// Guess if we are using a 32-bit or 64-bit platform
String elementType = (totalMemory >= 4L << 30 ? "Long" : "Int");
ApfloatContext.defaultProperties.setProperty(BUILDER_FACTORY, "org.apfloat.internal." + elementType + "BuilderFactory");
ApfloatContext.defaultProperties.setProperty(DEFAULT_RADIX, "10");
ApfloatContext.defaultProperties.setProperty(MAX_MEMORY_BLOCK_SIZE, String.valueOf(maxMemoryBlockSize));
ApfloatContext.defaultProperties.setProperty(CACHE_L1_SIZE, "8192");
ApfloatContext.defaultProperties.setProperty(CACHE_L2_SIZE, "262144");
ApfloatContext.defaultProperties.setProperty(CACHE_BURST, "32");
ApfloatContext.defaultProperties.setProperty(MEMORY_THRESHOLD, String.valueOf(memoryThreshold));
ApfloatContext.defaultProperties.setProperty(SHARED_MEMORY_TRESHOLD, String.valueOf(maxMemoryBlockSize / numberOfProcessors / 32));
ApfloatContext.defaultProperties.setProperty(BLOCK_SIZE, String.valueOf(blockSize));
ApfloatContext.defaultProperties.setProperty(NUMBER_OF_PROCESSORS, String.valueOf(numberOfProcessors));
ApfloatContext.defaultProperties.setProperty(FILE_PATH, "");
ApfloatContext.defaultProperties.setProperty(FILE_INITIAL_VALUE, "0");
ApfloatContext.defaultProperties.setProperty(FILE_SUFFIX, ".ap");
ApfloatContext.defaultProperties.setProperty(CLEANUP_AT_EXIT, "true");
// Set combination of default properties and properties specified in the resource bundle
ApfloatContext.globalContext = new ApfloatContext(loadProperties());
// ExecutorService depends on the properties so set it last
ApfloatContext.defaultExecutorService = getDefaultExecutorService();
ApfloatContext.globalContext.setExecutorService(ApfloatContext.defaultExecutorService);
}
}