Preventing Jit Inlining on a Method

Preventing JIT inlining on a method

You could use MethodImplAttribute and specify MethodImplOptions.NoInlining.

[MethodImpl(MethodImplOptions.NoInlining)]
void YourMethod()
{
// do something
}

Note that this still doesn't guarantee that you can get at the actual calling method as seen in the source code. Your method won't be inlined, but your method's caller could be inlined into its own caller, etc etc.

Is there anything that will stop java inlining a method?

If your method is too big, or has method which have been inlined too many times already, your method won't get inlined.

Can all methods be inlined, or are there certain elements of my code that would stop java from inlining a method?

There is a number of parameters which control this.

$ java -XX:+PrintFlagsFinal -version | grep Inline
bool C1ProfileInlinedCalls = true {C1 product}
intx FreqInlineSize = 325 {pd product}
bool IncrementalInline = true {C2 product}
bool Inline = true {product}
ccstr InlineDataFile = {product}
intx InlineSmallCode = 2000 {pd product}
bool InlineSynchronizedMethods = true {C1 product}
intx MaxInlineLevel = 9 {product}
intx MaxInlineSize = 35 {product}
intx MaxRecursiveInlineLevel = 1 {product}
intx Tier23InlineeNotifyFreqLog = 20 {product}
bool UseInlineCaches = true {product}
bool UseOnlyInlinedBimorphic = true {C2 product}

Of note: the MaxInlineSize limits the depth of inlining. In general this is not worth increasing as it can increase you code size and slow you program. The FrehInlineSize is the maximum size even frequently called methods will be inlined. I have found that increasing this a little can help for some programs.

The MaxInlineSize is the number of bytes that a small method needs to be to be inlined even if it is not frequently called.

Method is not inlined by the JIT compiler even though all criteria seems to be met

For some reason, after restarting VS, enabling and re-disabling the settings mentioned before as well as re-applying MethodImplOptions.AggressiveInlining, the method does now appear to be inlined (weird that it didn't before...). However it has added a couple of instructions that aren't there when you inline the if-conditions manually.

The efficiency/execution speed appears to stay the same, however. Hans Passant suggested that I replace the short-circuiting operators (where possible) with the regular | and &, which does reduce the speed gap from 2x to 1.5x. I'm guessing this is as good as it gets when it comes to JIT optimization.

return c == '\r' | (c == '\n' & (index == 0 || text[index - 1] != '\r'));

An interesting discovery I made (or, at least, interesting to me as I don't really understand how these Assembly-level optimizations work under the hood) was that when the same operator swap is done to the manually inlined conditions (inside GetLineNumberInline()), execution speed gets much worse.

The purpose of this adventure was to get as efficient code as possible without having to duplicate it everywhere I use it (since in the original code IsEndOfLine() is used multiple times throughout the project). In the end I think I'll stick to duplicating the IsEndOfLine() code only inside GetLineNumber(), as that proved to be the fastest in terms of execution speed.


I would like to thank the ones who took their time trying to help me (some comments have been removed), as while I didn't achieve the result that I thought JIT-optimized inlining would get me, I've still learned a lot that I didn't know before. Now I've at least gotten a small glimpse of what JIT optimization does under the hood and how it is much more complicated than what I could originally have imagined.


Complete benchmark results, for future reference (ordered by execution time):


Text length: 15882
Character position: 11912

Standard loop (inline): 00:00:00.1429526 (10000 à 0.0142 ms)
Standard loop (inline unsafe): 00:00:00.1642801 (10000 à 0.0164 ms)
Standard loop (inline + no short-circuit): 00:00:00.3250843 (10000 à 0.0325 ms)
Standard loop (AggressiveInlining): 00:00:00.3318966 (10000 à 0.0331 ms)
Standard loop (unsafe): 00:00:00.3605394 (10000 à 0.0360 ms)
Standard loop: 00:00:00.3859629 (10000 à 0.0385 ms)
Regex (Substring): 00:00:01.8794045 (10000 à 0.1879 ms)
Regex (MatchCollection loop): 00:00:02.4916785 (10000 à 0.2491 ms)

Resulting line: 284

/* "unsafe" is using pointers to access the string's characters */

class Program
{
const int RUNS = 10000;

static void Main(string[] args)
{
string text = "";
Random r = new Random();

//Some words to fill the string with.
string[] words = new string[] { "Hello", "world", "Inventory.MaxAmount 32", "+QUICKTORETALIATE", "TNT1 AABBCC 6 A_JumpIf(ACS_ExecuteWithResult(460, 0, 0, 0) == 0, \"See0\")" };

//Various line endings.
string[] endings = new string[] { "\r\n", "\r", "\n" };

/*
Generate text
*/
int lineCount = r.Next(256, 513);

for(int l = 0; l < lineCount; l++) {
int wordCount = r.Next(1, 4);
text += new string(' ', r.Next(4, 9));

for(int w = 0; w < wordCount; w++) {
text += words[wordCount] + (w < wordCount - 1 ? " " : "");
}

text += endings[r.Next(0, endings.Length)];
}

Console.WriteLine("Text length: " + text.Length);
Console.WriteLine();

/*
Initialize class and stopwatch
*/
TextParser parser = new TextParser(text);
Stopwatch sw = new Stopwatch();

List<int> numbers = new List<int>(); //Using a list to prevent the compiler from optimizing-away the "GetLineNumber" call.

/*
Test 1 - Standard loop
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumber((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop: ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 2 - Standard loop (with AggressiveInlining)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumber2((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop (AggressiveInlining): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 3 - Standard loop (with inline check)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberInline((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop (inline): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 4 - Standard loop (with inline and no short-circuiting)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberInline2((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop (inline + no short-circuit): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 5 - Standard loop (with unsafe check)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberUnsafe((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop (unsafe): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 6 - Standard loop (with inline + unsafe check)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberUnsafeInline((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Standard loop (inline unsafe): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 7 - Regex (with Substring)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberRegex((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Regex (Substring): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Test 8 - Regex (with MatchCollection loop)
*/
sw.Restart();
for(int x = 0; x < RUNS; x++) {
numbers.Add(parser.GetLineNumberRegex2((int)(text.Length * 0.75) + r.Next(-4, 4)));
}
sw.Stop();

Console.WriteLine("Line: " + numbers[0]);
Console.WriteLine("Regex (MatchCollection loop): ".PadRight(41) + sw.Elapsed.ToString() + " (" + numbers.Count + " à " + new TimeSpan(sw.Elapsed.Ticks / numbers.Count).TotalMilliseconds.ToString() + " ms)");
Console.WriteLine();

numbers = new List<int>();

/*
Tests completed
*/
Console.Write("All tests completed. Press ENTER to close...");
while(Console.ReadKey(true).Key != ConsoleKey.Enter);
}
}
public class TextParser
{
private static readonly Regex LineRegex = new Regex("\r\n|\r|\n", RegexOptions.Compiled);

private string text;

public TextParser(string text)
{
this.text = text;
}

/// <summary>
/// Returns whether the specified character index is the end of a line.
/// </summary>
/// <param name="index">The index to check.</param>
/// <returns></returns>
private bool IsEndOfLine(int index)
{
char c = text[index];
return c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'));
}

/// <summary>
/// Returns whether the specified character index is the end of a line.
/// </summary>
/// <param name="index">The index to check.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsEndOfLineAggressiveInlining(int index)
{
char c = text[index];
return c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'));
}

/// <summary>
/// Returns whether the specified character index is the end of a line.
/// </summary>
/// <param name="index">The index to check.</param>
/// <returns></returns>
private bool IsEndOfLineUnsafe(int index)
{
unsafe
{
fixed(char* ptr = text) {
char c = ptr[index];
return c == '\r' || (c == '\n' && (index == 0 || ptr[index - 1] != '\r'));
}
}
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumber(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

index = 0;
while(index < end) {
if(IsEndOfLine(index)) lineNumber++;
index++;
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumber2(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

index = 0;
while(index < end) {
if(IsEndOfLineAggressiveInlining(index)) lineNumber++;
index++;
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberInline(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

index = 0;
while(index < end) {
char c = text[index];
if(c == '\r' || (c == '\n' && (index == 0 || text[index - 1] != '\r'))) lineNumber++;
index++;
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberInline2(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

index = 0;
while(index < end) {
char c = text[index];
if(c == '\r' | (c == '\n' & (index == 0 || text[index - 1] != '\r'))) lineNumber++;
index++;
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberUnsafe(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

index = 0;
while(index < end) {
if(IsEndOfLineUnsafe(index)) lineNumber++;
index++;
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberUnsafeInline(int index)
{
if(index < 0 || index > text.Length) { throw new ArgumentOutOfRangeException("index"); }

int lineNumber = 1;
int end = index;

unsafe
{
fixed(char* ptr = text) {
index = 0;
while(index < end) {
char c = ptr[index];
if(c == '\r' || (c == '\n' && (index == 0 || ptr[index - 1] != '\r'))) lineNumber++;
index++;
}
}
}

return lineNumber;
}

/// <summary>
/// Returns the number of the line at the specified character index. Utilizes a Regex.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberRegex(int index)
{
return LineRegex.Matches(text.Substring(0, index)).Count + 1;
}

/// <summary>
/// Returns the number of the line at the specified character index. Utilizes a Regex.
/// </summary>
/// <param name="index">The index of the character which's line number to get.</param>
/// <returns></returns>
public int GetLineNumberRegex2(int index)
{
int lineNumber = 1;
MatchCollection mc = LineRegex.Matches(text);

for(int y = 0; y < mc.Count; y++) {
if(mc[y].Index >= index) break;
lineNumber++;
}

return lineNumber;
}
}

JVM disable inlining for a particular class or method

You can use the -XX:CompileCommand JVM option to control just-in-time compilation. The option can be used for excluding certain methods (or all methods of a class) to be compiled, and more. From the documentation:

Specifies a command to perform on a method. For example, to exclude the indexOf() method of the String class from being compiled, use the following:

-XX:CompileCommand=exclude,java/lang/String.indexOf

If you only want to prevent method inlining, you can use the dontinline command with the same syntax, e.g.


-XX:CompileCommand=dontinline,java/lang/String.indexOf

The same JVM option is used internally by the popular Java microbenchmark harness, JMH.

Can Java 8 default interface methods be inlined by the JIT compiler?

Eugene's example shows that default methods can be inlined.

In fact, I think that the criteria for inlining should be the same as for any other non-static method.

  • The size of the code to be inlined must be smaller than a tunable threshold.
  • The method must not be overridden by a method in any (currently loaded) subclass of the class or interface.

In your example, I think that inlining should be possible, assuming that this is all of the code involved in the example.

However, there may be other limitations with / in the specific JIT that is being used here. For example, a default method calling another default method might be an edge case that is rare enough that it was deemed not worth supporting. Another possible explanation is that the C1 compiler doesn't do deep monomorphic dispatch analysis / optimization.

And the flip-side of this is that this could be premature optimization ... unless your performance profiling has identified a specific hotspot in your code where inlining could make a significant difference. Normally, the best strategy is to leave this to the compiler. If you micro-optimize your code to give optimal performance for a given Java version, there is a good chance that you will need to redo the work when you change to a newer version.

JIT refuses to inline tiny methods

There is no way to require the JIT compiler inline your methods, aside from using a ahead-of-time source or bytecode transformation to inline the instructions before they ever reach the JIT.

If your algorithm is so sensitive to micro-optimizations that removing call instructions results in a substantial performance advantage, then you might consider rewriting the performance-critical sections of code in a different language that provides more extensive facilities for controlling that behavior. Based on the wording of your question, it appears that you are trying to force C# into a problem space which it was designed to avoid altogether.

Java: JIT method inlining

The short answer is whenever it wants.

Very often a JITC will inline small final or pseudo-final methods automatically, without first gathering any stats. This is because it's easy to see that the inlining actually saves code bytes vs coding the call (or at least that it's nearly a "wash").

Inlining truly non-final methods is not usually done unless stats suggest it's worthwhile, since inlined non-finals must be "guarded" somehow in case an unexpected subclass comes through.

As to the number of times something may be called before it's JITCed or inlined, that's highly variable, and is likely to vary even within a running JVM.

Run Time Inlining in C#?

C# the language doesn't do inlining, but the .NET CLR JIT compiler can.

Virtuals might be inline-able in a sealed class, but I'm not sure about non-sealed classes. I would assume not.

JIT optimizes before the execution of the code, when a function is first called. Because before the JIT goes to work, you don't have any code to execute. :P JIT happens only once on the first call to a function, not on every call to the function.

Note also that inlining is only done within an assembly (DLL). The JIT compiler will not copy code from another assembly to inline it into the code of this assembly.



Related Topics



Leave a reply



Submit