Há perda de desempenho pelo simples fato de usar a supressão de erros. Isto pode variar de versão para versão. Mas além do custo do erro ocorrer que já teria de qualquer forma, há um overhead adicional (teste realizado pelo Sean McArthur) para tratar a supressão. O código intermediário gerado quando o @ é usado fica maior, com instruções extras de custo nada desprezível em vários cenários. Além disto o PHP precisa acessar o INI e modificar temporariamente a exibição de erros e voltar ao estado original logo depois (parece que é ruim assim mesmo, mas podem ter melhorado isto).
Toda boa prática é pensada - nem sempre cumpre isto - para evitar que erros e dificuldade de manutenção aconteçam. Boa prática é saber o que está fazendo e não seguir receitas prontas, fazer porque ouviu falar.
A regra básica é nunca usar onde não precisa, porque está escondendo que é erro de programação mesmo, e o certo é consertar o problema. E usar onde precisa só quando não tem outra solução, porque é a forma que o PHP arrumou para te avisar que exige um tratamento do erro. Nestes casos faz até algum sentido de considerar o que expliquei acima.
Só pra acresentar: Você me deu a ideia de que, ao invés do "arroba gambiarra", posso então usar algo sofisticado como set_error_handler e transformá-lo numa exceção. Daí posso fazer um try/catch no unlink, por exemplo. – Wallace Maxters♦ 3/09/15 às 15:46