You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by "Mark S (Updated) (JIRA)" <de...@velocity.apache.org> on 2012/02/10 06:50:00 UTC

[jira] [Updated] (VELOCITY-817) Dynamic Templates with Caching

     [ https://issues.apache.org/jira/browse/VELOCITY-817?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Mark S updated VELOCITY-817:
----------------------------

    Description: 
I thought I would share the following code with the Apache Velocity community.


Problem Description:  I wanted to use Dynamic Templates though I found the following had massive performance implications.

-- Velocity Call Snippet --
String template = "message.to:  ${message.to} ";
StringWriter velocityWriter = new StringWriter();
velocityEngine.evaluate( velocityContext, velocityWriter, "LOG", template );
return velocityWriter.toString();
-- Velocity Call Snippet --


As such, I needed an alternate solution and guessed that if I could cache the Velocity templates used I could see big performance gains.

I initially looked to the org.apache.velocity.runtime.resource.loader.StringResourceLoader class to solve my problems.   The StringResourceLoader class requires an instance of the StringResourceRepository class, and the only implementation of StringResourceRepository is StringResourceRepositoryImpl.  Unfortunately, since the number of unique templates will grow over time, in time the StringResourceRepositoryImpl.resources Map will cause an out of memory error.

Why doesn't StringResourceRepositoryImpl use a cache with a maximum size?  Is a cache even needed as the Velocity ResourceCacheImpl will cache Templates?

+++++++++++++++++++++++++++++++++++++
In the end I settled on the following solution.  From my tests using YourKit Java Profiler I invoked the static Velocity wrapping method 100,000 times, and discovered a performance difference of around 75 fold (146,937ms vs 1,981ms).

Below is my solution.  Feedback appreciated.

+++++++++++++++++++++++++++++++++++++ 


Regards,
Mark

-- Velocity Engine Setup Snippet --
Properties p = new Properties();

p.setProperty( RuntimeConstants.INPUT_ENCODING, DEFAULT_ENCODING );
p.setProperty( RuntimeConstants.OUTPUT_ENCODING, DEFAULT_ENCODING );

p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CLASS,
       "org.apache.velocity.runtime.resource.ResourceManagerImpl" );
p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS,
       "org.apache.velocity.runtime.resource.ResourceCacheImpl" );
p.setProperty( RuntimeConstants.RESOURCE_MANAGER_DEFAULTCACHE_SIZE, "100" );

p.setProperty( RuntimeConstants.RESOURCE_LOADER, "string" );
p.setProperty( "string.resource.loader.class",
       "custom.SimpleStringResourceLoader" );
p.setProperty( "string.resource.loader.encoding", DEFAULT_ENCODING );
p.setProperty( "string.resource.loader.cache", Boolean.TRUE.toString() );
p.setProperty( "string.resource.loader.modificationCheckInterval", "-1" );

VelocityEngine engine = new VelocityEngine( p );
engine.init();
-- Velocity Engine Setup Snippet --

-- Velocity Call Snippet --
String template = "message.to:  ${message.to} ";
StringWriter velocityWriter = new StringWriter();
Template veTemplate = velocityEngine.getTemplate( template );
veTemplate.merge( velocityContext, velocityWriter );
return velocityWriter.toString();
-- Velocity Call Snippet --




-- Velocity Supporting Class Snippet --

package custom;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.apache.velocity.runtime.resource.util.StringResource;

public class SimpleStringResourceLoader extends ResourceLoader
{

    protected static int classMetricsLevel;
    protected static AtomicLong totalClassInstancesCreatedCount;
    protected static AtomicLong totalStringsGeneratedCount;

    static {
        classMetricsLevel = 1;
        totalClassInstancesCreatedCount = new AtomicLong();
        totalStringsGeneratedCount = new AtomicLong();
    }


    protected String encoding;


    public SimpleStringResourceLoader()
    {

        if ( classMetricsLevel > 0 )
        {
            totalClassInstancesCreatedCount.incrementAndGet();
        }

        this.encoding = "UTF-8";
    }

    @Override
    public void init( ExtendedProperties paramExtendedProperties )
    {
        String paramEncoding = paramExtendedProperties.getString( "encoding" );

        if ( null != encoding && encoding.trim().length() > 0 )
        {
            this.encoding = paramEncoding;
        }
    }

    @Override
    public InputStream getResourceStream( String contents )
    {
        if ( classMetricsLevel > 1 )
        {
            totalStringsGeneratedCount.incrementAndGet();
        }

        StringResource resource = new StringResource( contents, encoding );

        byte[] byteArray = null;
        try
        {
            byteArray = resource.getBody().getBytes( resource.getEncoding() );
            return new ByteArrayInputStream( byteArray );
        }
        catch ( UnsupportedEncodingException ue )
        {
            throw new VelocityException( "Failed to convert contents to String "
                    + resource.getEncoding(), ue );
        }

    }

    @SuppressWarnings( "unused" )
    @Override
    public boolean isSourceModified( Resource paramResource )
    {
        return false;
    }

    @SuppressWarnings( "unused" )
    @Override
    public long getLastModified( Resource paramResource )
    {
        return 0;
    }


    public String getEncoding()
    {
        return encoding;
    }

    public void setEncoding( String encoding )
    {
        this.encoding = encoding;
    }


    public static int getClassMetricsLevel()
    {
        return classMetricsLevel;
    }

    public static void setClassMetricsLevel( int classMetricsLevel )
    {
        SimpleStringResourceLoader.classMetricsLevel = classMetricsLevel;
    }

    public static void setTotalClassInstancesCreatedCount( long counterValue )
    {
        SimpleStringResourceLoader.totalClassInstancesCreatedCount.getAndSet( counterValue );
    }

    public static long getTotalClassInstancesCreatedCount()
    {
        return SimpleStringResourceLoader.totalClassInstancesCreatedCount.get();
    }


    public static void setTotalStringsGeneratedCount( long counterValue )
    {
        SimpleStringResourceLoader.totalStringsGeneratedCount.getAndSet( counterValue );
    }

    public static long getTotalStringsGeneratedCount()
    {
        return SimpleStringResourceLoader.totalStringsGeneratedCount.get();
    }

    @Override
    public String toString()
    {
        return "SimpleStringResourceLoader [encoding=" + encoding + "]";
    }

}

-- Velocity Supporting Class Snippet --



  was:
I thought I would share the following code with the Apache Velocity community.


Problem Description:  I wanted to use Dynamic Templates though I found the following had massive performance implications.

-- Velocity Call Snippet --
{code}
String template = "message.to:  ${message.to} ";
StringWriter velocityWriter = new StringWriter();
velocityEngine.evaluate( velocityContext, velocityWriter, "LOG", template );
return velocityWriter.toString();
{code}
-- Velocity Call Snippet --


As such, I needed an alternate solution and guessed that if I could cache the Velocity templates used I could see big performance gains.

I initially looked to the org.apache.velocity.runtime.resource.loader.StringResourceLoader class to solve my problems.   The StringResourceLoader class requires an instance of the StringResourceRepository class, and the only implementation of StringResourceRepository is StringResourceRepositoryImpl.  Unfortunately, since the number of unique templates will grow over time, in time the StringResourceRepositoryImpl.resources Map will cause an out of memory error.

Why doesn't StringResourceRepositoryImpl use a cache with a maximum size?  Is a cache even needed as the Velocity ResourceCacheImpl will cache Templates?

+++++++++++++++++++++++++++++++++++++
In the end I settled on the following solution.  From my tests using YourKit Java Profiler I invoked the static Velocity wrapping method 100,000 times, and discovered a performance difference of around 75 fold (146,937ms vs 1,981ms).

Below is my solution.  Feedback appreciated.

+++++++++++++++++++++++++++++++++++++ 


Regards,
Mark

-- Velocity Engine Setup Snippet --
{code}
Properties p = new Properties();

p.setProperty( RuntimeConstants.INPUT_ENCODING, DEFAULT_ENCODING );
p.setProperty( RuntimeConstants.OUTPUT_ENCODING, DEFAULT_ENCODING );

p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CLASS,
       "org.apache.velocity.runtime.resource.ResourceManagerImpl" );
p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS,
       "org.apache.velocity.runtime.resource.ResourceCacheImpl" );
p.setProperty( RuntimeConstants.RESOURCE_MANAGER_DEFAULTCACHE_SIZE, "100" );

p.setProperty( RuntimeConstants.RESOURCE_LOADER, "string" );
p.setProperty( "string.resource.loader.class",
       "custom.SimpleStringResourceLoader" );
p.setProperty( "string.resource.loader.encoding", DEFAULT_ENCODING );
p.setProperty( "string.resource.loader.cache", Boolean.TRUE.toString() );
p.setProperty( "string.resource.loader.modificationCheckInterval", "-1" );

VelocityEngine engine = new VelocityEngine( p );
engine.init();
{code}
-- Velocity Engine Setup Snippet --

-- Velocity Call Snippet --
{code}
String template = "message.to:  ${message.to} ";
StringWriter velocityWriter = new StringWriter();
Template veTemplate = velocityEngine.getTemplate( template );
veTemplate.merge( velocityContext, velocityWriter );
return velocityWriter.toString();
{code}
-- Velocity Call Snippet --



-- Velocity Supporting Class Snippet --
{code}

package custom;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.apache.velocity.runtime.resource.util.StringResource;

public class SimpleStringResourceLoader extends ResourceLoader
{

    protected static int classMetricsLevel;
    protected static AtomicLong totalClassInstancesCreatedCount;
    protected static AtomicLong totalStringsGeneratedCount;

    static {
        classMetricsLevel = 1;
        totalClassInstancesCreatedCount = new AtomicLong();
        totalStringsGeneratedCount = new AtomicLong();
    }


    protected String encoding;


    public SimpleStringResourceLoader()
    {

        if ( classMetricsLevel > 0 )
        {
            totalClassInstancesCreatedCount.incrementAndGet();
        }

        this.encoding = "UTF-8";
    }

    @Override
    public void init( ExtendedProperties paramExtendedProperties )
    {
        String paramEncoding = paramExtendedProperties.getString( "encoding" );

        if ( null != encoding && encoding.trim().length() > 0 )
        {
            this.encoding = paramEncoding;
        }
    }

    @Override
    public InputStream getResourceStream( String contents )
    {
        if ( classMetricsLevel > 1 )
        {
            totalStringsGeneratedCount.incrementAndGet();
        }

        StringResource resource = new StringResource( contents, encoding );

        byte[] byteArray = null;
        try
        {
            byteArray = resource.getBody().getBytes( resource.getEncoding() );
            return new ByteArrayInputStream( byteArray );
        }
        catch ( UnsupportedEncodingException ue )
        {
            throw new VelocityException( "Failed to convert contents to String "
                    + resource.getEncoding(), ue );
        }

    }

    @SuppressWarnings( "unused" )
    @Override
    public boolean isSourceModified( Resource paramResource )
    {
        return false;
    }

    @SuppressWarnings( "unused" )
    @Override
    public long getLastModified( Resource paramResource )
    {
        return 0;
    }


    public String getEncoding()
    {
        return encoding;
    }

    public void setEncoding( String encoding )
    {
        this.encoding = encoding;
    }


    public static int getClassMetricsLevel()
    {
        return classMetricsLevel;
    }

    public static void setClassMetricsLevel( int classMetricsLevel )
    {
        SimpleStringResourceLoader.classMetricsLevel = classMetricsLevel;
    }

    public static void setTotalClassInstancesCreatedCount( long counterValue )
    {
        SimpleStringResourceLoader.totalClassInstancesCreatedCount.getAndSet( counterValue );
    }

    public static long getTotalClassInstancesCreatedCount()
    {
        return SimpleStringResourceLoader.totalClassInstancesCreatedCount.get();
    }


    public static void setTotalStringsGeneratedCount( long counterValue )
    {
        SimpleStringResourceLoader.totalStringsGeneratedCount.getAndSet( counterValue );
    }

    public static long getTotalStringsGeneratedCount()
    {
        return SimpleStringResourceLoader.totalStringsGeneratedCount.get();
    }

    @Override
    public String toString()
    {
        return "SimpleStringResourceLoader [encoding=" + encoding + "]";
    }

}

{code}
-- Velocity Supporting Class Snippet --

    
> Dynamic Templates with Caching
> ------------------------------
>
>                 Key: VELOCITY-817
>                 URL: https://issues.apache.org/jira/browse/VELOCITY-817
>             Project: Velocity
>          Issue Type: Improvement
>          Components: Engine
>    Affects Versions: 1.7
>            Reporter: Mark S
>              Labels: performance
>   Original Estimate: 2h
>  Remaining Estimate: 2h
>
> I thought I would share the following code with the Apache Velocity community.
> Problem Description:  I wanted to use Dynamic Templates though I found the following had massive performance implications.
> -- Velocity Call Snippet --
> String template = "message.to:  ${message.to} ";
> StringWriter velocityWriter = new StringWriter();
> velocityEngine.evaluate( velocityContext, velocityWriter, "LOG", template );
> return velocityWriter.toString();
> -- Velocity Call Snippet --
> As such, I needed an alternate solution and guessed that if I could cache the Velocity templates used I could see big performance gains.
> I initially looked to the org.apache.velocity.runtime.resource.loader.StringResourceLoader class to solve my problems.   The StringResourceLoader class requires an instance of the StringResourceRepository class, and the only implementation of StringResourceRepository is StringResourceRepositoryImpl.  Unfortunately, since the number of unique templates will grow over time, in time the StringResourceRepositoryImpl.resources Map will cause an out of memory error.
> Why doesn't StringResourceRepositoryImpl use a cache with a maximum size?  Is a cache even needed as the Velocity ResourceCacheImpl will cache Templates?
> +++++++++++++++++++++++++++++++++++++
> In the end I settled on the following solution.  From my tests using YourKit Java Profiler I invoked the static Velocity wrapping method 100,000 times, and discovered a performance difference of around 75 fold (146,937ms vs 1,981ms).
> Below is my solution.  Feedback appreciated.
> +++++++++++++++++++++++++++++++++++++ 
> Regards,
> Mark
> -- Velocity Engine Setup Snippet --
> Properties p = new Properties();
> p.setProperty( RuntimeConstants.INPUT_ENCODING, DEFAULT_ENCODING );
> p.setProperty( RuntimeConstants.OUTPUT_ENCODING, DEFAULT_ENCODING );
> p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CLASS,
>        "org.apache.velocity.runtime.resource.ResourceManagerImpl" );
> p.setProperty( RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS,
>        "org.apache.velocity.runtime.resource.ResourceCacheImpl" );
> p.setProperty( RuntimeConstants.RESOURCE_MANAGER_DEFAULTCACHE_SIZE, "100" );
> p.setProperty( RuntimeConstants.RESOURCE_LOADER, "string" );
> p.setProperty( "string.resource.loader.class",
>        "custom.SimpleStringResourceLoader" );
> p.setProperty( "string.resource.loader.encoding", DEFAULT_ENCODING );
> p.setProperty( "string.resource.loader.cache", Boolean.TRUE.toString() );
> p.setProperty( "string.resource.loader.modificationCheckInterval", "-1" );
> VelocityEngine engine = new VelocityEngine( p );
> engine.init();
> -- Velocity Engine Setup Snippet --
> -- Velocity Call Snippet --
> String template = "message.to:  ${message.to} ";
> StringWriter velocityWriter = new StringWriter();
> Template veTemplate = velocityEngine.getTemplate( template );
> veTemplate.merge( velocityContext, velocityWriter );
> return velocityWriter.toString();
> -- Velocity Call Snippet --
> -- Velocity Supporting Class Snippet --
> package custom;
> import java.io.ByteArrayInputStream;
> import java.io.InputStream;
> import java.io.UnsupportedEncodingException;
> import java.util.concurrent.atomic.AtomicLong;
> import org.apache.commons.collections.ExtendedProperties;
> import org.apache.velocity.exception.VelocityException;
> import org.apache.velocity.runtime.resource.Resource;
> import org.apache.velocity.runtime.resource.loader.ResourceLoader;
> import org.apache.velocity.runtime.resource.util.StringResource;
> public class SimpleStringResourceLoader extends ResourceLoader
> {
>     protected static int classMetricsLevel;
>     protected static AtomicLong totalClassInstancesCreatedCount;
>     protected static AtomicLong totalStringsGeneratedCount;
>     static {
>         classMetricsLevel = 1;
>         totalClassInstancesCreatedCount = new AtomicLong();
>         totalStringsGeneratedCount = new AtomicLong();
>     }
>     protected String encoding;
>     public SimpleStringResourceLoader()
>     {
>         if ( classMetricsLevel > 0 )
>         {
>             totalClassInstancesCreatedCount.incrementAndGet();
>         }
>         this.encoding = "UTF-8";
>     }
>     @Override
>     public void init( ExtendedProperties paramExtendedProperties )
>     {
>         String paramEncoding = paramExtendedProperties.getString( "encoding" );
>         if ( null != encoding && encoding.trim().length() > 0 )
>         {
>             this.encoding = paramEncoding;
>         }
>     }
>     @Override
>     public InputStream getResourceStream( String contents )
>     {
>         if ( classMetricsLevel > 1 )
>         {
>             totalStringsGeneratedCount.incrementAndGet();
>         }
>         StringResource resource = new StringResource( contents, encoding );
>         byte[] byteArray = null;
>         try
>         {
>             byteArray = resource.getBody().getBytes( resource.getEncoding() );
>             return new ByteArrayInputStream( byteArray );
>         }
>         catch ( UnsupportedEncodingException ue )
>         {
>             throw new VelocityException( "Failed to convert contents to String "
>                     + resource.getEncoding(), ue );
>         }
>     }
>     @SuppressWarnings( "unused" )
>     @Override
>     public boolean isSourceModified( Resource paramResource )
>     {
>         return false;
>     }
>     @SuppressWarnings( "unused" )
>     @Override
>     public long getLastModified( Resource paramResource )
>     {
>         return 0;
>     }
>     public String getEncoding()
>     {
>         return encoding;
>     }
>     public void setEncoding( String encoding )
>     {
>         this.encoding = encoding;
>     }
>     public static int getClassMetricsLevel()
>     {
>         return classMetricsLevel;
>     }
>     public static void setClassMetricsLevel( int classMetricsLevel )
>     {
>         SimpleStringResourceLoader.classMetricsLevel = classMetricsLevel;
>     }
>     public static void setTotalClassInstancesCreatedCount( long counterValue )
>     {
>         SimpleStringResourceLoader.totalClassInstancesCreatedCount.getAndSet( counterValue );
>     }
>     public static long getTotalClassInstancesCreatedCount()
>     {
>         return SimpleStringResourceLoader.totalClassInstancesCreatedCount.get();
>     }
>     public static void setTotalStringsGeneratedCount( long counterValue )
>     {
>         SimpleStringResourceLoader.totalStringsGeneratedCount.getAndSet( counterValue );
>     }
>     public static long getTotalStringsGeneratedCount()
>     {
>         return SimpleStringResourceLoader.totalStringsGeneratedCount.get();
>     }
>     @Override
>     public String toString()
>     {
>         return "SimpleStringResourceLoader [encoding=" + encoding + "]";
>     }
> }
> -- Velocity Supporting Class Snippet --

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators: https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@velocity.apache.org
For additional commands, e-mail: dev-help@velocity.apache.org