--- a/net/minecraft/world/level/levelgen/DensityFunctions.java
+++ b/net/minecraft/world/level/levelgen/DensityFunctions.java
@@ -506,6 +_,16 @@
         );
         private static final float ISLAND_THRESHOLD = -0.9F;
         private final SimplexNoise islandNoise;
+        // Paper start - Perf: Optimize end generation
+        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 - Perf: Optimize end generation
 
         public EndIslandDensityFunction(long seed) {
             RandomSource randomSource = new LegacyRandomSource(seed);
@@ -518,15 +_,31 @@
             int i1 = z / 2;
             int i2 = x % 2;
             int i3 = z % 2;
-            float f = 100.0F - Mth.sqrt(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(noise, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation
             for (int i4 = -12; i4 <= 12; i4++) {
                 for (int i5 = -12; i5 <= 12; i5++) {
-                    long l = i + i4;
-                    long l1 = i1 + i5;
+                    long l = i + i4; final int chunkX = (int) l; // Paper - OBFHELPER
+                    long l1 = i1 + i5; final int chunkZ = (int) l1; // Paper - OBFHELPER
+                    // Paper start - Perf: Optimize end generation by using a noise cache
+                    final long chunkKey = net.minecraft.world.level.ChunkPos.asLong(chunkX, chunkZ);
+                    final int cacheIndex = (int) it.unimi.dsi.fastutil.HashCommon.mix(chunkKey) & 8191;
+                    float f1 = Float.MIN_VALUE; // noise value
+                    if (cache.keys[cacheIndex] == chunkKey) {
+                        // Use cache
+                        f1 = cache.values[cacheIndex];
+                    } else {
+                        // Vanilla function
                     if (l * l + l1 * l1 > 4096L && noise.getValue(l, l1) < -0.9F) {
-                        float f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
+                        f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
+                    }
+                        cache.keys[cacheIndex] = chunkKey;
+                        cache.values[cacheIndex] = f1;
+                    }
+                    if (f1 != Float.MIN_VALUE) {
+                        // Paper end - Perf: Optimize end generation
                         float f2 = i2 - i4 * 2;
                         float f3 = i3 - i5 * 2;
                         float f4 = 100.0F - Mth.sqrt(f2 * f2 + f3 * f3) * f1;
