Lo estás haciendo bien; es lento, porque la abstracción añadida del ORM significa que no puedes hacer el tipo de optimizaciones que te gustaría.
Dicho esto, el EntityManager se vuelve lento en transacciones tan grandes. Si no los necesita absolutamente a todos en una gran transacción, probablemente pueda obtener un código de mayor rendimiento al vaciar () y luego borrar () el EM cada 20-200 iteraciones de su ciclo.
Si eso no le brinda suficiente rendimiento, la única alternativa que se me ocurre es volver al código personalizado que ejecuta SQL personalizado directamente en su DBMS.
Sé que no es una gran respuesta, pero al menos puedo decirte que no estás loco.
------ editar ------
Del artículo oficial de Doctrine2 sobre Procesamiento por lotes
:
También hay una diferencia significativa en el rendimiento cuando se usa remoto frente a local base de datos ya que la sobrecarga de enviar cada consulta al servidor remoto es bastante grande. La sobrecarga es mucho menor al usar la base de datos local gracias a las transacciones y las optimizaciones de la base de datos. (por ejemplo, 70 segundos reducidos a 300 ms en el caso del ejemplo en la pregunta)