--- a/net/minecraft/world/level/levelgen/DensityFunctions.java
+++ b/net/minecraft/world/level/levelgen/DensityFunctions.java
@@ -490,6 +490,16 @@
         public static final KeyDispatchDataCodec<DensityFunctions.EndIslandDensityFunction> CODEC = KeyDispatchDataCodec.of(MapCodec.unit(new DensityFunctions.EndIslandDensityFunction(0L)));
         private static final float ISLAND_THRESHOLD = -0.9F;
         private final SimplexNoise islandNoise;
+        // Paper start
+        private static final class NoiseCache {
+            public long[] keys = new long[8192];
+            public float[] values = new float[8192];
+            public NoiseCache() {
+                java.util.Arrays.fill(keys, Long.MIN_VALUE);
+            }
+        }
+        private static final ThreadLocal<java.util.Map<SimplexNoise, NoiseCache>> noiseCache = ThreadLocal.withInitial(java.util.WeakHashMap::new);
+        // Paper end
 
         public EndIslandDensityFunction(long seed) {
             RandomSource randomSource = new LegacyRandomSource(seed);
@@ -502,15 +512,29 @@
             int j = z / 2;
             int k = x % 2;
             int l = z % 2;
-            float f = 100.0F - Mth.sqrt((float)(x * x + z * z)) * 8.0F;
+            float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow
             f = Mth.clamp(f, -100.0F, 80.0F);
 
+            NoiseCache cache = noiseCache.get().computeIfAbsent(sampler, noiseKey -> new NoiseCache()); // Paper
             for(int m = -12; m <= 12; ++m) {
                 for(int n = -12; n <= 12; ++n) {
                     long o = (long)(i + m);
                     long p = (long)(j + n);
-                    if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) {
-                        float g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F;
+                    // Paper start - Significantly improve end generation performance by using a noise cache
+                    long key = net.minecraft.world.level.ChunkPos.asLong((int) o, (int) p);
+                    int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191;
+                    float g = Float.MIN_VALUE;
+                    if (cache.keys[index] == key) {
+                        g = cache.values[index];
+                    } else {
+                        if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) {
+                            g = (Mth.abs((float) o) * 3439.0F + Mth.abs((float) p) * 147.0F) % 13.0F + 9.0F;
+                        }
+                        cache.keys[index] = key;
+                        cache.values[index] = g;
+                    }
+                    if (g != Float.MIN_VALUE) {
+                        // Paper end
                         float h = (float)(k - m * 2);
                         float q = (float)(l - n * 2);
                         float r = 100.0F - Mth.sqrt(h * h + q * q) * g;
